You can add a customized serialization of any class
, by calling
the TJSONSerializer. RegisterCustomSerializer
class method. Two
callbacks are to be defined for a specific class type, and will be used to
serialize or un-serialize the object instance. The callbacks are class methods
(procedure() of object
), and not plain functions (for some evolved
objects, it may have sense to use a context during serialization).
In the current implementation of this feature, callbacks expect low-level
implementation. That is, their implementation code shall follow function
JSONToObject()
patterns, i.e. calling low-level
GetJSONField()
function to decode the JSON content, and follow
function TJSONSerializer.WriteObject()
patterns, i.e.
aSerializer.Add/AddInstanceName/AddJSONEscapeString
to encode the
class instance as JSON.
Note that the process is called outside the "{{...}
" JSON
object layout, allowing any serialization scheme: even a class content can be
serialized as a JSON string, JSON array or JSON number, on request.
For instance, we'd like to customize the serialization of this class
(defined in SynCommons.pas
):
TFileVersion = class protected fDetailed: string; fBuildDateTime: TDateTime; public Major: Integer; Minor: Integer; Release: Integer; Build: Integer; BuildYear: integer; Main: string; published property Detailed: string read fDetailed write fDetailed; property BuildDateTime: TDateTime read fBuildDateTime write fBuildDateTime; end;
By default, since it has been defined within $M+ ... $M-
conditionals, RTTI is available for the published
properties (just
as if it were inheriting from TPersistent
). That is, the default
JSON serialization will be for instance:
{"Detailed":"1.2.3.4","BuildDateTime":"1911-03-14T00:00:00"}
This is what is expected when serialized within a TSynLog
content, or for main use.
We would like to serialize this class
as such:
{"Major":1,"Minor":2001,"Release":3001,"Build":4001,"Main":"1","BuildDateTime":"1911-03-14"}
We will therefore define the Writer callback, as such:
class procedure TCollTstDynArray.FVClassWriter(const aSerializer: TJSONSerializer; aValue: TObject; aOptions: TTextWriterWriteObjectOptions); var V: TFileVersion absolute aValue; begin aSerializer.AddJSONEscape(['Major',V.Major,'Minor',V.Minor,'Release',V.Release, 'Build',V.Build,'Main',V.Main,'BuildDateTime',DateTimeToIso8601Text(V.BuildDateTime)]); end;
Most of the JSON serialization work will be made within the
AddJSONEscape
method, expecting the JSON object description as an
array of name/value pairs.
Then the associated Reader callback could be, for instance:
class function TCollTstDynArray.FVClassReader(const aValue: TObject; aFrom: PUTF8Char; var aValid: Boolean): PUTF8Char; var V: TFileVersion absolute aValue; Values: TPUtf8CharDynArray; begin aValid := false; aFrom := JSONDecode(aFrom,['Major','Minor','Release','Build','Main','BuildDateTime'],Values); if aFrom=nil then exit; V.Major := GetInteger(Values[0]); V.Minor := GetInteger(Values[1]); V.Release := GetInteger(Values[2]); V.Build := GetInteger(Values[3]); V.Main := UTF8DecodeToString(Values[4],StrLen(Values[4])); V.BuildDateTime := Iso8601ToDateTimePUTF8Char(Values[5]); aValid := true; result := aFrom; end;
Here, the JSONDecode
function will un-serialize the JSON object
into an array of PUTF8Char
values, without any memory allocation
(in fact, Values[]
will point to un-escaped and #0 terminated
content within the aFrom
memory buffer. So decoding is very
fast.
Then, the registration step will be defined as such:
TJSONSerializer.RegisterCustomSerializer(TFileVersion, TCollTstDynArray.FVClassReader,TCollTstDynArray.FVClassWriter);
If you want to disable the custom serialization, you may call the same method as such:
TJSONSerializer.RegisterCustomSerializer(TFileVersion,nil,nil);
This will reset the JSON serialization of the specified class to the default
serializer (i.e. writing of published
properties).
The above code uses some low-level functions of the framework (i.e.
AddJSONEscape
and JSONDecode
) to implement
serialization as a JSON object, but you may use any other serialization scheme,
on need. That is, you may serialize the whole class instance just as one JSON
string or numerical value, or even a JSON array. It will depend of the
implementation of the Reader and Writer registered
callbacks.
Feedback and comments are welcome on our forum.