JSON Persistence
TMS FNC Core can save object state to JSON and load it back into object instances. Use this for component settings, application state, and plain persistent objects that expose the values you want to store through published properties.
The persistence layer is shared by FNC products and is available through framework-prefixed units such as FMX.TMSFNCPersistence, VCL.TMSFNCPersistence, and WEBLib.TMSFNCPersistence.
Required Units
Use the persistence unit when you want direct load and save methods:
uses
FMX.TMSFNCPersistence;
Use the types unit when you want object helper methods such as JSON, ToJSON, Log, SaveToJSONFile, and LoadFromJSONFile:
uses
FMX.TMSFNCTypes;
Persisted Properties
JSON persistence reads and writes published properties. Move values that must be persisted into the published section of your class, the same way Delphi form streaming requires published component properties.
Nested object properties are supported when the nested object can be reached from a published property. String lists and supported generic lists are written as JSON arrays.
type
TPersonAddress = class(TPersistent)
private
FPostalCode: string;
FAddressLocality: string;
FAddressRegion: string;
FStreetAddress: string;
published
property AddressLocality: string read FAddressLocality write FAddressLocality;
property AddressRegion: string read FAddressRegion write FAddressRegion;
property PostalCode: string read FPostalCode write FPostalCode;
property StreetAddress: string read FStreetAddress write FStreetAddress;
end;
TPerson = class(TPersistent)
private
FAddress: TPersonAddress;
FColleagues: TStringList;
FEmail: string;
FName: string;
public
constructor Create;
destructor Destroy; override;
published
property Address: TPersonAddress read FAddress;
property Colleagues: TStringList read FColleagues;
property Email: string read FEmail write FEmail;
property Name: string read FName write FName;
end;
class TPersonAddress : public TPersistent
{
private:
String FPostalCode;
String FAddressLocality;
String FAddressRegion;
String FStreetAddress;
__published:
__property String AddressLocality = {read=FAddressLocality, write=FAddressLocality};
__property String AddressRegion = {read=FAddressRegion, write=FAddressRegion};
__property String PostalCode = {read=FPostalCode, write=FPostalCode};
__property String StreetAddress = {read=FStreetAddress, write=FStreetAddress};
};
class TPerson : public TPersistent
{
private:
TPersonAddress *FAddress;
TStringList *FColleagues;
String FEmail;
String FName;
public:
__fastcall TPerson();
__fastcall ~TPerson();
__published:
__property TPersonAddress* Address = {read=FAddress};
__property TStringList* Colleagues = {read=FColleagues};
__property String Email = {read=FEmail, write=FEmail};
__property String Name = {read=FName, write=FName};
};
Load From JSON
Use TTMSFNCPersistence.LoadSettingsFromStream when the JSON is already available as a stream.
var
Person: TPerson;
Stream: TStringStream;
begin
Person := TPerson.Create;
Stream := TStringStream.Create(JsonSample);
try
TTMSFNCPersistence.LoadSettingsFromStream(Person, Stream);
finally
Stream.Free;
Person.Free;
end;
end;
TPerson *Person = new TPerson();
TStringStream *Stream = new TStringStream(JsonSample);
try
{
TTMSFNCPersistence::LoadSettingsFromStream(Person, Stream);
}
__finally
{
delete Stream;
delete Person;
}
Use TTMSFNCObjectPersistence.LoadObjectFromString when the JSON is available as a string.
var
Person: TPerson;
begin
Person := TPerson.Create;
try
TTMSFNCObjectPersistence.LoadObjectFromString(Person, JsonSample);
finally
Person.Free;
end;
end;
TPerson *Person = new TPerson();
try
{
TTMSFNCObjectPersistence::LoadObjectFromString(Person, JsonSample);
}
__finally
{
delete Person;
}
When FMX.TMSFNCTypes is in the uses list, you can also assign the JSON string through the object helper:
var
Person: TPerson;
begin
Person := TPerson.Create;
try
Person.JSON := JsonSample;
finally
Person.Free;
end;
end;
TPerson *Person = new TPerson();
try
{
Person->JSON = JsonSample;
}
__finally
{
delete Person;
}
Save To JSON
Use TTMSFNCPersistence.SaveSettingsToFile to write object state directly to a JSON file.
var
Person: TPerson;
begin
Person := TPerson.Create;
try
Person.JSON := JsonSample;
Person.Name := 'Joe Heart';
TTMSFNCPersistence.SaveSettingsToFile(Person, 'TPerson.json');
finally
Person.Free;
end;
end;
TPerson *Person = new TPerson();
try
{
Person->JSON = JsonSample;
Person->Name = "Joe Heart";
TTMSFNCPersistence::SaveSettingsToFile(Person, "TPerson.json");
}
__finally
{
delete Person;
}
Use TTMSFNCObjectPersistence.SaveObjectToString when you need the generated JSON as a string.
var
Person: TPerson;
Json: string;
begin
Person := TPerson.Create;
try
Json := TTMSFNCObjectPersistence.SaveObjectToString(Person);
finally
Person.Free;
end;
end;
TPerson *Person = new TPerson();
try
{
String Json = TTMSFNCObjectPersistence::SaveObjectToString(Person);
}
__finally
{
delete Person;
}
The object helper also exposes ToJSON:
var
Person: TPerson;
Json: string;
begin
Person := TPerson.Create;
try
Json := Person.ToJSON;
finally
Person.Free;
end;
end;
TPerson *Person = new TPerson();
try
{
String Json = Person->ToJSON();
}
__finally
{
delete Person;
}
Class Type Metadata
Saved JSON can include a $type property. The persistence layer uses this class name when it needs to create object instances while loading JSON.
Register classes that may be created from $type metadata:
initialization
RegisterClass(TPerson);
RegisterClass(TPersonAddress);
Class registration is especially important when JSON contains nested objects, collection items, or generic list items that must be instantiated while loading.
Collections
Extend the TPerson example with a TCollection-based relations list:
TPersonRelation = class(TCollectionItem)
private
FName: string;
FDescription: string;
published
property Name: string read FName write FName;
property Description: string read FDescription write FDescription;
end;
TPersonRelations = class(TCollection)
private
function GetItem(Index: Integer): TPersonRelation;
procedure SetItem(Index: Integer; const Value: TPersonRelation);
public
constructor Create;
function Add: TPersonRelation;
property Items[Index: Integer]: TPersonRelation read GetItem write SetItem; default;
end;
class TPersonRelation : public TCollectionItem
{
private:
String FName;
String FDescription;
__published:
__property String Name = {read=FName, write=FName};
__property String Description = {read=FDescription, write=FDescription};
};
class TPersonRelations : public TCollection
{
private:
TPersonRelation* __fastcall GetItem(int Index);
void __fastcall SetItem(int Index, TPersonRelation* const Value);
public:
__fastcall TPersonRelations();
TPersonRelation* __fastcall Add();
__property TPersonRelation* Items[int Index] = {read=GetItem, write=SetItem};
};
Add Relations: TPersonRelations as a published property of TPerson. The collection is serialised as a JSON array of objects, each with a "$type" property:
"Relations": [
{ "$type": "TPersonRelation", "Description": "Brother", "Name": "John Doe" },
{ "$type": "TPersonRelation", "Description": "Mother", "Name": "Mia Reyes" }
]
Loading Without $type
When the source JSON has no "$type" keys on array items, implement ITMSFNCBaseListIO on the collection so the persistence layer knows which item class to create:
TPersonRelations = class(TCollection, ITMSFNCBaseListIO)
function GetItemClass: TClass;
// IInterface boilerplate:
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
function TPersonRelations.GetItemClass: TClass;
begin
Result := TPersonRelation;
end;
class TPersonRelations : public TCollection, public ITMSFNCBaseListIO
{
public:
virtual TClass __fastcall GetItemClass();
// IInterface boilerplate: QueryInterface, _AddRef, _Release
};
TClass __fastcall TPersonRelations::GetItemClass()
{
return __classid(TPersonRelation);
}
Also implement ITMSFNCBasePersistenceIO on the root object to control item construction:
TPerson = class(TInterfacedPersistent, ITMSFNCBasePersistenceIO)
public
Relations: TCollection; // owns the TPersonRelation items
protected
function CreateObject(const AClassName: string; const ABaseClass: TClass): TObject;
end;
function TPerson.CreateObject(const AClassName: string; const ABaseClass: TClass): TObject;
begin
Result := nil;
if AClassName = 'TPersonRelation' then
Result := TPersonRelation.Create(Relations);
end;
class TPerson : public TInterfacedPersistent, public ITMSFNCBasePersistenceIO
{
public:
TCollection *Relations; // owns the TPersonRelation items
protected:
virtual TObject* __fastcall CreateObject(const String AClassName, const TClass ABaseClass);
};
TObject* __fastcall TPerson::CreateObject(const String AClassName, const TClass ABaseClass)
{
TObject *Result = nullptr;
if (AClassName == "TPersonRelation")
Result = new TPersonRelation(Relations);
return Result;
}
Register the item class so the persistence layer can instantiate it:
initialization
RegisterClass(TPersonRelation);
Loading With $type
When the JSON includes "$type" on every object, use TTMSFNCObjectPersistence.LoadObjectFromString instead of the class helper — the class helper ignores "$type", so collection items will not be created without the interfaces above.
Generics
Replace TPersonRelations = class(TCollection) with a generic object list:
TPersonRelation = class(TPersistent)
// same published properties as before
procedure Assign(Source: TPersistent); override;
end;
TPersonRelations = TObjectList<TPersonRelation>;
class TPersonRelation : public TPersistent
{
// same published properties as before
public:
virtual void __fastcall Assign(TPersistent *Source);
};
typedef TObjectList__1<TPersonRelation*> TPersonRelations;
The loading and saving mechanism is identical to TCollection. The same ITMSFNCBaseListIO / ITMSFNCBasePersistenceIO interfaces apply when the JSON omits "$type".
TDictionary Support
TObjectDictionary<string, T> is supported. Keys must be strings. Each entry is serialised as a single-key JSON object inside an array:
[
{ "1": { "$type": "TMyObject", "MyProperty": "Value 1" } },
{ "2": { "$type": "TMyObject", "MyProperty": "Value 2" } }
]
Supported Generic Types
| Type | Notes |
|---|---|
TObjectList<TObject> / TList<TObject> |
Items must be TPersistent descendants |
TList<string> |
Serialised as a JSON string array |
TList<Integer> |
Serialised as a JSON integer array |
TList<Double> |
Serialised as a JSON number array |
Undo / Redo Manager
TTMSFNCUndoManager keeps a stack of JSON snapshots for any persistent object. Add TMSFNCUndo to the uses list:
uses
FMX.TMSFNCUndo;
Create one manager per object — each instance manages exactly one object:
var
p: TPerson;
u: TTMSFNCUndoManager;
begin
p := TPerson.Create;
u := TTMSFNCUndoManager.Create(p);
try
TTMSFNCObjectPersistence.LoadObjectFromString(p, jsonSample);
u.PushState('init'); // save clean state
p.Name := 'ERROR';
u.PushState('error_data'); // save changed state
u.Undo; // revert to 'init'
u.Redo; // re-apply 'error_data'
finally
u.Free;
p.Free;
end;
end;
TPerson *p = new TPerson();
TTMSFNCUndoManager *u = new TTMSFNCUndoManager(p);
try
{
TTMSFNCObjectPersistence::LoadObjectFromString(p, jsonSample);
u->PushState("init"); // save clean state
p->Name = "ERROR";
u->PushState("error_data"); // save changed state
u->Undo(); // revert to "init"
u->Redo(); // re-apply "error_data"
}
__finally
{
delete u;
delete p;
}
TTMSFNCUndoManager API
| Member | Description |
|---|---|
PushState(AActionName) |
Push a JSON snapshot of the managed object onto the history stack |
Undo |
Restore the previous state |
Redo |
Re-apply the next state |
CanUndo |
True if an undo step is available |
CanRedo |
True if a redo step is available |
NextUndoAction |
Name of the action that would be undone |
NextRedoAction |
Name of the action that would be redone |
ClearUndoStack |
Discard all history |
MaxStackCount |
Maximum entries kept (default: 20) |
Related API
TTMSFNCPersistenceTTMSFNCPersistence.LoadSettingsFromStreamTTMSFNCPersistence.SaveSettingsToFileTTMSFNCObjectPersistenceTTMSFNCObjectPersistence.LoadObjectFromStringTTMSFNCObjectPersistence.SaveObjectToString