Blog
All Blog Posts | Next Post | Previous PostDiving deeper: JSON persistence, part 1/4: Basics
Tuesday, August 2, 2022
Intro
TMS FNC Core is a universal core layer for creating rich visual and non-visual components for VCL, FMX, LCL and WEB core apps. A major part of TMS FNC Core is the ability to save and load objects to and from JSON. There is a complete JSON parser, reader and writer included. Components inheriting from the base TTMSFNCCustomControl, TTMSFNCCustomComponent class and including the unit (FMX.)(VCL.)(WEBLib.)(LCL)TMSFNCPersistence.pas get JSON persistence by default. Even when only using TMS FNC Core, objects that are not directly related to FNC can be persisted with a couple of lines of code.
The "Diving Deeper: JSON persistence" blog series will consist out of four parts:
- Basic object persistence
- Collections
- Generic lists & dictionaries
- Undo / Redo Manager
The blog series will use a TPerson class and eventually lead into a class structure that has all the bits and pieces together required for JSON persistence. Note that when referring to unit names, the prefix of the framework ((FMX.)(VCL.)(WEBLib.)(LCL)) will not be included, for readability purposes. The code snippets are written in FMX as a default framework, but can easily be ported to other frameworks. If you have any questions during the blog series, don't hesitate to ask them in the comments section, or by using our support channels.
Units
After installing TMS FNC Core, the most important units are the TMSFNCPersistence.pas and the TMSFNCTypes.pas unit. There are a couple of class helper functions in the TMSFNCUtils unit, but that is of lesser importance and mostly revolve around parsing raw JSON content.
- TMSFNCPersistence.pas: Unit containing interfaces and class functions to save and load JSON data to and from objects
- TMSFNCTypes.pas: Unit containing some class helpers to assign JSON data, which internally map on the TMSFNCPersistence.pas unit.
TMSFNCPersistence.pas is a unit that contains 2 important classes, TTMSFNCObjectPersistence and TTMSFNCPersistence. TTMSFNCObjectPersistence has a class function to save and a class procedure to load JSON data to and from an object.
TTMSFNCObjectPersistence = class public class function SaveObjectToString(AObject: TObject): string; class procedure LoadObjectFromString(AObject: TObject; AString: string); end;
TTMSFNCPersistence is the base class that provides a lot of functionality to detect property names, types and the ability to read and write custom properties. In this series we will not go into much detail, but the details of this class might be covered in a future blog post. For the purposes of these blog series, the focus lies on the TTMSFNCObjectPersistence class, the TTMSFNCPersistence class basic load/save methods and the class helpers available in the TMSFNCTypes.pas unit.
Published properties
Before we get started, there is one important requirement to make sure that our object and its properties get persisted. Only published properties will be persisted. Internally, a cross-platform method is used and currently maps on published properties only due to technical reasons. This might expand to public properties as well in the future, but for now, it's important to move properties to the published section in order to persist them, the same way as is required when creating components that have properties persisted in the form file.
Basic object persistence: TPerson
Let's get started by defining our TPerson class
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; FBirthDate: string; FName: string; FEmail: string; FTelephone: string; FGender: string; FNationality: string; FJobTitle: string; FURL: 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 JobTitle: string read FJobTitle write FJobTitle; property Name: string read FName write FName; property BirthDate: string read FBirthDate write FBirthDate; property Gender: string read FGender write FGender; property Nationality: string read FNationality write FNationality; property Telephone: string read FTelephone write FTelephone; property URL: string read FURL write FURL; end;
As you can see, TPerson has a property Address which is again an object of type TPersonAddress, additionally it also contains a Colleague: TStringList property which will be mapped on a JSON array. Nested object properties are also handled out of the box.
Inspecting TPerson
When creating an instance of TPerson, there is a nice utility helper method that can log an object in JSON to the IDE debug output window. When including the TMSFNCTypes unit, call [Object].Log; to output the JSON as shown in the following sample.
var p: TPerson; begin p := TPerson.Create; try p.Log; finally p.Free; end; end;
{ "$type": "TPerson", "Address": { "$type": "TPersonAddress", "AddressLocality": "", "AddressRegion": "", "PostalCode": "", "StreetAddress": "" }, "BirthDate": "", "Colleagues": [], "Email": "", "Gender": "", "JobTitle": "", "Name": "", "Nationality": "", "Telephone": "", "URL": "" }
Loading Data
const jsonSample = '{' + '"$type": "TPerson",' + '"address":{' + '"$type": "TPersonAddress",' + '"addressLocality":"Colorado Springs",' + '"addressRegion":"CO",' + '"postalCode":"80840",' + '"streetAddress":"100 Main Street"' + '},' + '"colleagues":[' + '"http://www.example.com/JohnColleague.html",' + '"http://www.example.com/JameColleague.html"' + '],' + '"email":"info@example.com",' + '"jobTitle":"Research Assistant",' + '"name":"Jane Doe",' + '"birthDate":"1979-10-12",' + '"gender":"female",' + '"nationality":"Albanian",' + '"telephone":"(123) 456-6789",' + '"url":"http://www.example.com"' + '}';
As explained there are multiple ways to map the JSON data onto the TPerson object.
1. Use the TTMSFNCPersistence class
Add the unit *TMSFNCPersistence to the uses list (* = FMX., LCL, VCL., WEBLib.), and use the following code:
var p: TPerson; s: TStringStream; begin p := TPerson.Create; s := TStringStream.Create(jsonSample); try TTMSFNCPersistence.LoadSettingsFromStream(p, s); finally s.Free; p.Free; end; end;
2. Use the TTMSFNCObjectPersistence class
An alternative is to use the class TTMSFNCObjectPersistence that maps JSON to the object. (string variable or constant only)
var p: TPerson; begin p := TPerson.Create; try TTMSFNCObjectPersistence.LoadObjectFromString(p, jsonSample); finally p.Free; end; end;
3. Use the object class helper in *TMSFNCTypes unit
var p: TPerson; begin p := TPerson.Create; try p.JSON := jsonSample; finally p.Free; end; end;
Saving Data
Writing to JSON is as easy as reading. Simply use SaveSettingsToFile or SaveSettingsToStream or use the JSON object class helper to get the JSON from the object. For each of the above three load methods, there are an equivalent in save methods as well. For testing purposes, we want to change the name of "Jane Doe", to "Joe Heart" first to demonstrates how the result of the JSON output changes accordingly.
var p: TPerson; begin p := TPerson.Create; try p.JSON := jsonSample; p.Name := 'Joe Heart'; TTMSFNCUtils.Log(p.JSON); //or TTMSFNCPersistence.SaveSettingsToFile(p, 'TPerson.json'); finally p.Free; end; end;
Alternatively, the TTMSFNCObjectPersistence class can be used to save the object to a JSON string
var p: TPerson; j: string; begin p := TPerson.Create; try j := TTMSFNCObjectPersistence.SaveObjectToString(p); finally p.Free; end; end;
Or by using the TMSFNCTypes unit class helpers
var p: TPerson; j: string; begin p := TPerson.Create; try j := p.ToJSON; finally p.Free; end; end;
$type property
The above data loading expects the TPerson object to exist and be accessible. In some situations however, the object will not be available when loading the JSON data, or some situations might expect the object to be loaded alongside the JSON data. As you might already have noticed, in the JSON output of the object, there are properties named "$type", which map on the classes of the objects in JSON. These properties are used for the JSON persistence in FNC to be able to instantiate those classes. To make sure the object(s) can be created when required, it's good practice to register your classes. In case of our TPerson class, this translates to:
RegisterClass(TPerson); RegisterClass(TPersonAddress);
Feedback
This blog content might sound familiar and indeed, it is! Some while ago we wrote this blog post, which basically almost covers the first part and we wanted to start of with something familiar. We had a series of questions afterwards and thought about relaunching the FNC JSON persistence in a 4 part blog series. Next up will be how to handle collections, so stay tuned for more to come! As always, please leave a comment or if you have any questions, don't hesitate to ask us!
Pieter Scheldeman
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post