In fact, there are two ways of specifying a custom JSON serialization for record:
- When setting a custom dynamic array JSON serializer, the associated record will also use the same Reader and Writer callbacks;
- By setting explicitly serialization callbacks for the TypeInfo() of the record, with the very same TTextWriter. RegisterCustomJSONSerializer method.

For instance, you can serialize the following record definition:

  TSQLRestCacheEntryValue = record
    ID: integer;
    TimeStamp: cardinal;
JSON: RawUTF8;
  end;

With the following code:

  TTextWriter.RegisterCustomJSONSerializer(TypeInfo(TSQLRestCacheEntryValue),
    TTestServiceOrientedArchitecture.CustomReader,
    TTestServiceOrientedArchitecture.CustomWriter);

The expected format will be as such:

 {"ID":1786554763,"TimeStamp":323618765,"JSON":"D:\TestSQL3.exe"}

Therefore, the writer callback could be:

class procedure TTestServiceOrientedArchitecture.CustomWriter(
  const aWriter: TTextWriter; const aValue);
var V: TSQLRestCacheEntryValue absolute aValue;
begin
  aWriter.AddJSONEscape(['ID',V.ID,'TimeStamp',Int64(V.TimeStamp),'JSON',V.JSON]);
end;

In the above code, the cardinal field named TimeStamp is type-casted to a Int64: in fact, as stated by the documentation of the AddJSONEscape method, an array of const will handle by default any cardinal as an integer value (this is a limitation of the Delphi compiler). By forcing the type to be an Int64, the expected cardinal value will be transmitted, and not a wrongly negative versions for numbers > $7fffffff.

On the other side, the corresponding reader callback would be like:

class function TTestServiceOrientedArchitecture.CustomReader(P: PUTF8Char;
  var aValue; out aValid: Boolean): PUTF8Char;
var V: TSQLRestCacheEntryValue absolute aValue;
    Values: TPUtf8CharDynArray;
begin
result := JSONDecode(P,['ID','TimeStamp','JSON'],Values);
  if result=nil then
    aValid := false else begin
    V.ID := GetInteger(Values[0]);
    V.TimeStamp := GetCardinal(Values[1]);
    V.JSON := Values[2];
    aValid := true;
  end;
end;

Note that the callback signature used for records matches the one used for dynamic arrays serializations - see this article - as it will be shared between the two of them.

Even if older versions of Delphi are not able to generate the needed RTTI information for such serialization, the mORMot framework offers a common way of implementing any custom serialization of records.
When records are used as Data Transfer Objects within services (which is a good idea in common SOA implementation patterns), such a custom serialization format can be handy, and makes more natural service consumption with AJAX clients.

See this commit about the feature.