For instance, we would like to serialize a dynamic array of the following record:

  TFV = packed record
    Major, Minor, Release, Build: integer;
    Main, Detailed: string;
  end;
  TFVs = array of TFV;

With the default serialization, such a dynamic array will be serialized as a Base64 encoded binary buffer. This won't be easy to understand from an AJAX client, for instance.

In order to add a custom serialization for this kind of record, we need to implement the two needed callbacks. Our expected format will be a JSON array of all fields, i.e.:

 [1,2001,3001,4001,"1","1001"]

We may have used another layout, e.g. using JSONEncode() function and a JSON object layout, or any other valid JSON content.

Here comes the writer:

class procedure TCollTstDynArray.FVWriter(const aWriter: TTextWriter; const aValue);
var V: TFV absolute aValue;
begin
  aWriter.Add('[%,%,%,%,"%","%"]',
    [V.Major,V.Minor,V.Release,V.Build,V.Main,V.Detailed],twJSONEscape);
end;

This event will write one entry of the dynamic array, without the last ',' (which will be appended by TTextWriter.AddDynArrayJSON).
In this method, twJSONEscape is used to escape the supplied string content as a valid JSON string (with double quotes and proper UTF-8 encoding).

Of course, the Writer is easier to code than the Reader itself:

class function TCollTstDynArray.FVReader(P: PUTF8Char; var aValue;
  out aValid: Boolean): PUTF8Char;
var V: TFV absolute aValue;
begin // '[1,2001,3001,4001,"1","1001"],[2,2002,3002,4002,"2","1002"],...'
  aValid := false;
  result := nil;
  if (P=nil) or (P^<>'[') then
    exit;
  inc(P);
  V.Major := GetNextItemCardinal(P);
  V.Minor := GetNextItemCardinal(P);
  V.Release := GetNextItemCardinal(P);
  V.Build := GetNextItemCardinal(P);
  V.Main := UTF8ToString(GetJSONField(P,P));
  V.Detailed := UTF8ToString(GetJSONField(P,P));
  if P=nil then
    exit;
  aValid := true;
  result := P; // ',' or ']' for last item of array
end;

The reader method shall return a pointer to the next separator of the JSON input buffer just after this item (either ',' or ']').

The registration process itself is as simple as:

  TTextWriter.RegisterCustomJSONSerializer(TypeInfo(TFVs),
    TCollTstDynArray.FVReader,TCollTstDynArray.FVWriter);

Then, from the user code point of view, this dynamic array handling won't change: once registered, the JSON serializers are used everywhere in the framework, as soon as this type is registered globally.

Here is a Writer method using a JSON object layout:

class procedure TCollTstDynArray.FVWriter2(const aWriter: TTextWriter; const aValue);
var V: TFV absolute aValue;
begin
  aWriter.AddJSONEscape(['Major',V.Major,'Minor',V.Minor,'Release',V.Release,
    'Build',V.Build,'Main',V.Main,'Detailed',V.Detailed]);
end;

This will create some JSON content as such:

 {"Major":1,"Minor":2001,"Release":3001,"Build":4001,"Main":"1","Detailed":"1001"}

Then the corresponding Reader callback could be written as:

class function TCollTstDynArray.FVReader2(P: PUTF8Char; var aValue;
  out aValid: Boolean): PUTF8Char;
var V: TFV absolute aValue;
    Values: TPUtf8CharDynArray;
begin
  aValid := false;
  P := JSONDecode(P,['Major','Minor','Release','Build','Main','Detailed'],Values);
  if P=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.Detailed := UTF8DecodeToString(Values[5],StrLen(Values[5]));
  aValid := true;
  result := P; // ',' or ']' for last item of array
end;

Most of the JSON decoding process is performed within the JSONDecode() function, which will let Values[] point to null-terminated un-escaped content within the P^ buffer. In fact, such process will do only one memory allocation (for Values[]), and will therefore be very fast.

You can define now your custom JSON serializers, starting for the above code as reference.

This JSON serialization will indeed be used in our main ORM to support dynamic arrays as enhanced properties (stored as BLOB), and in the interface-based SOA architecture of the framework, for content transmission.

Feedback and comments are welcome in our forum, just as usual.