NG-Serialization library provides an ability to change the output representation of serializing value. This can be done writing custom converter, which is essentially a descendant of TCustomConverter base class. The converter writer should declare TCustomConverter base class descendant, and override and implement all its abstract methods:
•GetReadMode method implementation should just return a value indicating in which mode the converter works: rmNormal for normal mode or rmFillRead for fill-read mode.
•Write method implementation should serialize provided V value using provided S serializer object. Implementation should use public serializer's methods, such as Value, BeginObject/Prop/EndObject and BeginArray/EndArray, to serialize V value in a way it wants.
•Read method implementation should de-serialize value using provided D de-serializer object. Implementation should use public de-serializer's methods, such as HasNext, Value, BeginObject/EndObject and BeginArray/EndArray, to de-serialize value in a way it wants. If the converter works in normal mode it should treat V parameter as output parameter and assign de-serialized value to it. However, if the converter works in fill-read mode it should treat V parameter as input parameter and use provided V value to fill it with de-serializing information. Thus, V parameter should never be treated as bi-directional (in-out).
To associate custom converter class with the converted type ConverterAttribute should be used like this:
type
TMyCollConverter = class(TCustomConverter)
public
function GetReadMode: TReadMode; override;
procedure Write(S: TSerializer; const V); override;
procedure Read(D: TDeserializer; var V); override;
end;
[Converter(TMyCollConverter)]
TMyCollection = class
//...
end;
Following is a list of potential use-cases where custom converters can be used:
•Collections. Since collections usually represented by objects, there are required to implement custom converter to serialize collection object as an array of element (items) instead of collection object itself with all its public properties.
•Variant like data, which can be implemented as a record or an object. Look for example below.
•Serializing references to business objects as the corresponding object's IDs.
Consider the following declaration:
type
TMyKind = (mkNull, mkBool, mkInteger, mkString);
TMyVariant = record
private
// Implementation is not shown.
public
property Kind: TMyKind read GetKind write SetKind;
property AsBool: Boolean read GetAsBool write SetAsBool;
property AsInteger: Integer read GetAsInteger write SetAsInteger;
property AsString: string read GetAsString write SetAsString;
end;
As seen from declaration there are no need to serialize all TMyVariant properties, because only one of tree data properties is matter. So, the custom converter can be used to achieve more readable serialization result, like this:
<MyVariant>
<Kind>Integer</Kind>
<Value>7</Value>
</MyVariant>
Following is a converter code used to achieve above result:
type
TMyVarConverter = class(TConverter)
public
function GetReadMode: TReadMode; override;
procedure Write(S: TSerializer; const V); override;
procedure Read(D: TDeserializer; var V); override;
end;
function TMyVarConverter.GetReadMode: TReadMode;
begin
Result := rmNormal;
end;
procedure TMyVarConverter.Write(S: TSerializer; const V);
var
mv: ^TMyVariant;
begin
mv := @TMyVariant(V);
S.BeginObject('MyVariant', False);
S.Prop('Kind').Value<string>(KindToStr(mv.Kind));
case mv.Kind of
mkNull: ; // Do nothing.
mkBool: S.Prop('Value').Value<Boolean>(mv.AsBool);
mkInteger: S.Prop('Value').Value<Integer>(mv.AsInteger);
mkString: S.Prop('Value').Value<String>(mv.AsString);
end;
S.EndObject;
end;
procedure TMyVarConverter.Read(D: TDeserializer; var V);
var
mv: TMyVariant;
tp: string;
begin
D.BeginObject(tp);
mv.Kind := StrToKind(D.Prop('Kind').Value<string>);
case mv.Kind of
mkNull: ; // Do nothing.
mkBool: mv.AsBool := D.Prop('Value').Value<Boolean>;
mkInteger: mv.AsInteger := D.Prop('Value').Value<Integer>;
mkString: mv.AsString := D.Prop('Value').Value<String>;
end;
D.EndObject;
TMyVariant(V) := mv;
end;
After converter is written is should be associated with TMyVariant class using ConverterAttribute. Thus, TMyVariant declaration should be changed as follows:
type
[Converter(TMyVarConverter)]
TMyVariant = record
...
end;