Introduction

Navigation:  »No topics above this level«

Introduction

Previous pageReturn to chapter overviewNext page

Serialization process is controlled by special serializer/de-serializer objects. Such objects are implemented for every output format:

 

XML serialization is performed by TXmlSerializer and TXmlDeserializer object,

JSON serialization is performed by TJsonSerializer and TJsonDeserializer object,

Binary serialization is performed by TBinarySerializer and TBinaryDeserializer objects.

 

These helper objects should be created (and later destroyed) by the user. Serializer objects descends from common TSerializer base class, which declare common serialization methods and properties. As well, de-serializer objects descends from TDeserializer common base class.

 

The library is designed so, that interfaces of these common base classes are sufficient for most usage, and its recommended to be used as a parameter types of various custom MySerialize/MyDeserialize methods. However, its important to note, that format specific serializer objects has some additional, non-common, stuff. First of all, it constructors, which takes different arguments for different formats. Then, some additional methods/properties, such as FlushBuffer for binary serializer or RootValue for XML serializer are also had to be used to achieve format specific needs; however they are designed to be usable at the same routine level, where serializer objects are created/destroyed.

 

Following is the very simple example, which shows how to serialize and de-serialize TPoint value using XML:

 

Serialize code:

 

var

  s: TXmlSerializer;

  p: TPoint;

begin

  p.X := 7;

  p.Y := 9;

 

  s := TXmlSerializer.Create(MyXmlDoc.Node);

  try

    s['MyPoint'].Value<TPoint>(p);

    MyXmlDoc.SaveToFile('...');

  finally

    s.Free;

  end;

end;

 

Resulting XML:

 

<MyPoint>
  <X>7</X>
  <Y>9</Y>
</MyPoint>

 

De-serialize back code:

 

var

  d: TXmlDeserializer;

  p: TPoint;

begin

  d := TXmlDeserializer.Create(MyXmlDoc.Node);

  try

    p := d.Value<TPoint>;

  finally

    d.Free;

  end;

end;

 

What can be serialized

 

The following types are supported for serialization:

 

All numeric types, including integer types as well as floating point types.

String types, such as AnsiString, WideString, UnicodeString, ect.

Char types, such as AnsiChar, WideChar, Char, ect.

Records: all public fields and properties are serialized by default; however, this can be overridden, using SerializableAttribute and TransientAttribute.

Classes: all public and published fields and properties are serialized by default; however, this can be overridden, using SerializableAttribute and TransientAttribute. Serialization includes all inherited fields and properties; however, inherited field and properties can be excluded from serialization using NoInheritedAttribute.  Serializable classes should have public parameter-less constructor; it is used by serialization engine to create new object instance while de-serializing object values.

Enumerations, sets.

Arrays: both static and dynamic arrays are supported. Multi-dimensional arrays are also supported, and treated as arrays of arrays.

 

Following is an example of overriding default serializable status of fields:

 

type

  TMyObject = class

  protected

    [Serializable]

    s: string;  // Will be serialized.

  public

    X: Integer;

   [Transient]

    Y: Integer; // Will not be serialized.

  end;

 

If only some special fields or properties need to be serialized, the type can be marked with TransientAttribute as a whole, which will mark all public and published properties to be transient by default:

 

type

  [Transient]

  TMyObject2 = class

  public

    X: Integer; // Will not be serialized.

    Y: Integer; //

    [Serializable]

    S: string;  // Will be serialized.

  end;

 

Here, it should be noted, that NG-Serialization engine will only process fields and properties, which are visible via Delphi RTTI. Delphi, does not generate RTTI for all fields or properties; actually, to use protected or private ones, the user should use {$RTTI ...} Delphi directive to force Delphi compiler to include required RTTI.

 

Aliasing

 

Sometimes names of types, fields, properties or even array elements should be changed to improve readability of human readable output formats, such as XML or JSON. This can be accomplished using AliasAttribute (ElemAliasAttribute for array elements).

 

Example:

 

type

  [ElemAlias('Number')]

  TMyArray = array of Integer;

 

  TMyObject = class

  public

    [Alias('Numbers')]

    property Arr: TMyArray;

  end;

 

Resulting XML:

 

<MyObject>

  <Numbers>

    <Number>3</Number>

    <Number>5</Number>

    <Number>9</Number>

  </Numbers>

</MyObject>

 

Internal data model

 

As seen from the example above, the main method to use is the Value method. Note, that it is a template method and the serialized/de-serialized type should be specified. In current section other useful serializer/de-serializer methods will be described.

 

Despite the fact, that the package is made multi-format, it has common internal data model, which is primarily grabbed from JSON. So, at the core level, serialization engine works with the following data abstractions:

 

Value - can be a simple typed value, for example, Integer or String, or a complex value such as array or object.

Object - a complex value, which is a set of properties. Each property has a name and value. Serialization engine converts Delphi class instances and records to internal objects.

Array - a complex value, which is a set of elements - an ordered sequence of values, which (unlike properties) has no names. Serialization engine converts Delphi arrays and collections (via custom converters) to internal arrays.

 

Its important to note that the value itself has no name. Actually, the only place where a name is associated with a value - are object properties. This implies the following very important idea: Root level values are unnamed in the internal data model. And since the library allows to store more than one root-level value with a single serializer object (calling Value method several times), such values are treated sequentially; and, actually, should be read back in the same order. Its important to understand sequential internal nature of the library.

 

However, some formats, such as XML format, requires the name (tag) to be provided for every stored value; thus, even root-level values or array elements should have a name. The library API include routines to provide such additional names, however these names are just ignored in more compatible formats such as binary stream or JSON format. The example of such API is RootValue indexed property of TXmlSerializer class; in the example above it used implicitly in expression s['MyPoint'], because its a default Delphi property.

 

JSON format also conflicts slightly with internal data model. In fact, JSON allows only single root level value; more than one value cannot be represented with valid JSON string. To overcome this issue, JSON serializer's constructor takes an address of (array) buffer, into which all root level values are stored. Its up to the user, of what to do later with those values. However, if a single JOSN value capable to be converted into valid JSON string is still needed as a result of serialization, the user should wrap several values into JSON array or object manually, e.g. calling BeginArray/EndArray or BeginObject/EndObject/Prop methods.

 

As has been already noted, any single value can be serialized using Value<> method. This case includes all possible values, even complex values such as objects or arrays. However there is an additional API which allows to serialize object or array like data without using (declaring) corresponding Delphi types. This API designed to be used primarily in the following two cases:

 

In high level routines, to format data manually; usually the API is used directly in the procedure which creates/destroys serialization object.

In custom converters, which are classes descended from TCustomConverter base class.

 

So, to serialize object like data, BeginObject/EndObject and Prop additional serializer's methods can be used. The following example, shows how to serialize point data manually, without using TPoint Delphi type:

 

Serialize code:

 

var

  s: TXmlSerializer;

begin

  s := TXmlSerializer.Create(MyXmlDoc.Node);

  try

    s['MyPoint'].BeginObject('', False);

    s.Prop('X').Value<Integer>(7);

    s.Prop('Y').Value<Integer>(9);

    s.EndObject;

  finally

    s.Free;

  end;

end;

 

Resulting XML:

 

<MyPoint>
  <X>7</X>
  <Y>9</Y>
</MyPoint>

 

Note, how RootValue property, which is again used implicitly in s['MyPoint'] expression, is used with the BeginObject method in a single code line; as well, Prop method is used with Value method in a single code line also. This is possible because RootValue and Prop always returns Self object which is serializer, which makes the resulting code more readable.

 

So, to serialize array like data, BeginArray/EndArray additional serializer's methods can be used in a manner similar to manual object data serialization. The following example, shows how to serialize some integers as an array, without using real array Delphi type:

 

Serialize code:

 

var

  s: TXmlSerializer;

begin

  s := TXmlSerializer.Create(MyXmlDoc.Node);

  try

    s['MyArray'].BeginArray('Item');

    s.Value<Integer>(3);

    s.Value<Integer>(7);

    s.Value<Integer>(9);

    s.EndArray;

  finally

    s.Free;

  end;

end;

 

Resulting XML:

 

<MyArray>
  <Item>3</Item>
  <Item>7</Item>
  <Item>9</Item>
</MyArray>

 

The rule with these additional API is that BeginObject/BeginArray methods can be used anywhere the Value method is used; more precisely, its valid to use them to serialize property values, thus - after a Prop method call, or, while serializing array elements. So, following this way its possible to have object or array property values, as well, as array of objects or array of arrays.