Inheritance

Navigation:  »No topics above this level«

Inheritance

Previous pageReturn to chapter overviewNext page

NG-Serialization library supports Object Pascal inheritance. This means the following: if a field (or property) of some class type TAncestor hold an object value of some another class type TDescendant, where TDescendant is a descendant of TAncestor, then the field value will be serialized/de-serialized correctly; that is all serializable properties declared in TDescendant class type will be written in the output medium, and, as well, the value object of class type TDescendant will be created during de-serialization, and all its serialized properties will be read back. For example, consider the following type declarations, and myobj object variable initialization code:

 
type
  TAncestor = class
  public
    X: Integer;
  end;
 
  TDescendant = class(TAncestor)
  public
    Y: Integer;
  end;
 
  TMyObject = class
  public
    O: TAncestor; // The type of the field is TAncestor...
  end;
 
var
  myobj: TMyObject;
begin
  myobj   := TMyObject.Create;
  myobj.o := TDescendant.Create; // ...But, the assigned value 

                                 //    is of TDescendant type.
  //...
end;

 

Serializing myobj using XML serializer will result in the following XML:

 

<MyObject>
  <O Class="TDescendant">
    <X>0</X>
    <Y>0</Y>
  </O>
</MyObject>

 

There are three things to note here:

 

First, is a Class attribute, which stores property value class name; this value is required to be stored, because it used while de-serialization process; generally, the class of object value is stored only when its different from the corresponding field or property type class.

Second, class name, stored as a Class attribute value can be adjusted using AliasAttibute, making XML more readable.

Third, as has been described above, Y field, which is a subclass field, is also stored in XML.

 

Classes Registration

 

This section will discuss, how meta-data is used by serialization engine and, why, sometimes, an explicit classes registration is required.

 

For each serialized/de-serialized type the engine initializes and stores in memory additional information. This information contains read via RTTI field and property set, serialization related attributes, ect. In general, this information dramatically improves run-time performance of the engine. Type related meta-data is initialized lazily and stored in a global dictionary. For following discussion imagine a very simple serialization case:

 

s.Value<TPoint>(p);

 

In the code above, a point, which is a record of TPoint type is serialized. In this a reference to TPoint type info is explicitly provided to Value template method, so the engine will be able to initialize related meta-data on-the-fly. Moreover, the type info of all point fields, which are in this case two Integer fields, are also available from the parent TPoint type info. So, serializing a point this way do not require to register types explicitly.

 

The same way meta-data can be initialized during de-serialization:

 

p := d.Value<TPoint>;

 

As seen from the code, a reference to TPoint type is provided here too. So, in most cases, including serializing TMyObject object with TDescendant sub-object from the first example, do not require explicit types registration.

 

However, de-serialization of the produced in the first example XML can fail. Consider the following de-serialization code:

 

myobj := d.Value<TMyObject>;

 

In this code TMyObject type info is provided to the engine, however, this type info do not contain a reference to TDescendant type info, because the field O is, actually, of TAncestor type only. So, in this case its required to register TDescendant class explicitly. TMetadata class should be used to achieve this:

 

TMetadata.RegisterClass(TDescendant);

 

To register several classes at once TMetadata.RegisterClasses method can be used instead. Usually, such explicit registration is performed at application startup, for example, in initialization clause of some application unit.

 

There is another one use case, where explicit class registration is required. Consider the code:

 

var
  obj: TObject;
 
obj := d.Value<TObject>;
if obj is TMyObject then
  ; //...

 

This code de-serializes an object of TMyObject class, providing just TObject as a template argument for Value method. Sometimes, code like this is useful, e.g. where no a-priory knowledge about de-serializing object type exists. Here again, all possible object classes should be registered explicitly.

 

Default Value Class

 

Recall the XML, produced by the first example:

 

<MyObject>
  <O Class="TDescendant">
   ...
  </O>
</MyObject>

 

As seen from XML text, TDescendant value object class is stored in XML as Class attribute. Since in some human readable formats, like XML or JSON this affects readability, engine stores the name of the class only when necessary. The same can be said about other formats, such as binary stream; however, the main goal here is to reduce output stream size.

 

The default rule is to store class name, if value run-time class is different from field or property class type. However, this rule can be adjusted using DefClassAttribute, which allows to specify default class, different from the field or property class type:

 

type

  TAncestor = class

  public

    X: Integer;

  end;

 

  TDescendant = class(TAncestor)

  public

    Y: Integer;

  end;

 

  TMyObject = class

  public

    [DefClass(TDescendant)]

    O: TAncestor; // The type of the field is TAncestor...

  end;

 

The same rule is applied to root-level object values. For example, consider the code:

 

s.Value<TMyObject>(obj);

 

This code will store obj class name, if the object run-time type, which is obj.ClassType, is not equal to provided (as template argument) TMyObject class.

 

Suppressing Inherited Fields and Properties

 

Sometimes it is useful to descend serialization related classes from some other standard or third-party classes; for example, VCL classes such as TPersistent or TComponent, can be used as ancestors. Usually, these classes contains huge amount of properties, which, actually, are not intended to be serialized. Moreover, in such cases serialization engine can raise exceptions, because not all that properties even can be serialized.

 

In such situations, all ancestor fields and properties can be suppressed applying NoInheritedAttribute to the type:

 

type

  [NoInherited]

  TMyObject = class(TComponent)

  public

    X: Integer;

    S: string;

  end;

 

Inheritance and Custom Converters

 

Inheritance is not automatically supported with custom converters. So, for example, if TAncestor class is serialized using custom converter, the implementation of the converter should be aware of all possible values, including values of TDescendant type (if required, of course); and moreover, the converter should be able to distinguish these values based on de-serializing information.

 

Generally, this was a design decision, mainly motivated by the fact, that custom converter can convert an object value to any type of internal value; for example - to array or just number.