NG SerializationPack Guide
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.
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.