Table of Contents

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;

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;

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;

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;

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;

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;

The object helper also exposes ToJSON:

var
  Person: TPerson;
  Json: string;
begin
  Person := TPerson.Create;
  try
    Json := Person.ToJSON;
  finally
    Person.Free;
  end;
end;

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)
public
  constructor Create;
  function Add: TPersonRelation;
  property Items[Index: Integer]: TPersonRelation read GetItem write SetItem; default;
end;

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: QueryInterface, _AddRef, _Release
end;

function TPersonRelations.GetItemClass: TClass;
begin
  Result := TPersonRelation;
end;

Also implement ITMSFNCBasePersistenceIO on the root object to control item construction:

TPerson = class(TInterfacedPersistent, ITMSFNCBasePersistenceIO)
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;

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>;

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;

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)

See Also