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.