NG SerializationPack Guide


Fill-Read Mode


In Delphi object oriented programming its common to have sub-objects owned by its parent objects; such sub-objects commonly created in the corresponding parent object's constructor and destroyed in parent object's destructor. Usually, they are not re-created during parent object's life time. In some cases, these sub-objects are exposed via public parent object's property.

 

There are a lot of examples of such sub-objects in Delphi VCL:

 

Font property, which is an object of type TFont.

Constraints property, which is also an object.

All collections, such as Items, Lines, ect.

 

Following is a simple example of declarations containing sub-object property:

 

type

  TSubObject = class

  public

    X: Integer;

  end;

 

  TMyObject = class

  private

    FSubObject: TSubObject;

  public

    constructor Create;

    destructor Destroy; override;

    property SubObject: TSubObject read FSubObject;

  end;

 

constructor TMyObject.Create;

begin

  FSubObject := TSubObject.Create;

end;

 

destructor TMyObject.Destroy; override;

begin

  FSubObject.Free;

end;

 

The main problem with these sub-objects is that they cannot be handled by serialization engine as usually. Its incorrect to assign newly created sub-object instance during de-serialization. Moreover, the corresponding public property can be read-only at all. So, the example above will not be serializable.

 

To make the code above serializable, FillReadAttribute should be used. It specifies that serialization engine should use so-called fill-read mode. Serialization of fill-read properties works the same way as in normal mode: property value, which is sub-object reference is read and its properties are serialized. However, de-serialization in fill-read mode works differently:

 

Property value, which is sub-object is read from the property.

And this existing sub-object value is used to de-serialize sub-object properties.

 

FillReadAttribute can be applied to sub-object property like this:

 

type

  TMyObject = class

  private

    FSubObject: TSubObject;

  public

    constructor Create;

    destructor Destroy; override;

    [FillRead]

    property SubObject: TSubObject read FSubObject;

  end;

 

Or, alternatively, it can be applied to sub-object class itself:

 

type

  [FillRead]

  TSubObject = class

  public

    X: Integer;

  end;

 

In this case fill-read mode will be used with all properties of TSubObject type. There are several things should be kept in mind, while applying FillReadAttribute to the whole sub-object type:

 

First, this way fill-read mode can't be overridden back to normal mode in descendants; nor it can be overridden at the property level.

Second, since serialization engine never attempt to replace an instance of sub-object with another one, it actually, never create such new instances. Thus, the parameter-less constructor is not required for serializable classes, which are marked with FillReadAttribute.

 

Fill-read mode with non-object types and root level values

 

For working with values in fill-read more manually, special overload of the de-serializer's Value method can be used. This method is a procedure, which does not return any value, but instead take a single var parameter. Use it as follows:

 

obj := TSubObject.Create;

d.Value<TSubObject>(obj); // Read already created obj in fill-read mode.

 

Fill-read mode can be used with values of any type, not only object values. There are some restrictions, depending of type used. For example, it is fully correct to have a dynamic array property marked as fill-read. Since, dynamic array is a reference type in Delphi, the reference to array data will be read and filled with de-serializing elements. The restriction here is that array length can't be changed during de-serialization, because this will cause array data reallocation; so, if de-serializing array element count will differ from actual dynamic array length, the exception will be raised during de-serialization.

 

Fill-read mode can be used even with so-called value-types, such as numbers, strings, static arrays or records. However, the value here should be "L-value", that is it should be possible to take value address. Following there is a list of cases where it possible:

 

Value is a class field.

Value is field of L-value record.

Value is an element of L-Value static array.

Value is a dynamic array element.

A reference to value is passed to de-serializer's Value method as a var parameter.

 

Note, that property values are not considered to be L-values.