Custom Converters

Navigation:  »No topics above this level«

Custom Converters

Previous pageReturn to chapter overview

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.

 

Example of Serialization of Variant Like Data

 

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;