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.:
We may have used another layout, e.g. using
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
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
The registration process itself is as simple as:
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:
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); V.Minor := GetInteger(Values); V.Release := GetInteger(Values); V.Build := GetInteger(Values); V.Main := UTF8DecodeToString(Values,StrLen(Values)); V.Detailed := UTF8DecodeToString(Values,StrLen(Values)); 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
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
Feedback and comments are welcome in our forum, just as usual.