Skip to content

Versioning and field numbers

Vladyslav Taranov edited this page Mar 5, 2016 · 11 revisions

Often it's required to be able to serialize objects in one version of an application and deserialize in another.

Fields

AqlaSerializer doesn't store field names but instead it uses integer numbers to identify each field ("field number" or "tag"). E.g. you may rename a field or shift to property but as long as it has the same tag the binary output will not change.

  • A tag must be a non-zero positive integer value.
  • Tags must be unique within a single type but the same numbers can be reused in hierarchy types.
  • Lower tag values take less space.

You may specify a tag on each field using attribute mapping. In this example Value is written using tag 5:

[SerializableMember(5)]
public int Value { get; set; }

If you remove any field or add a new (with new tag) the format will still be compatible between your application versions.

When you don't specify [SerializableMember] for a property it gets registered automatically with an incrementing tag starting from ImplicitFirstTag. You may change this behavior by specifying [SerializableType(ImplicitFields = ..., ImplicitFirstTag = ...)].

Hierarchy

Each derived types is registered on its base type as an additional field which has a tag the same way like any other field:

[SerializableType]
[SerializeDerivedType(1, typeof(Person))]
class EntityBase
{
    [SerializableMember(2)] // tag 1 is already in use for Person
    public int Id { get; set; }
}

[SerializableType]
[SerializeDerivedType(1, typeof(Customer))]
class Person : EntityBase
{
    [SerializableMember(2)]  // tag 1 is already in use for Customer
    public string Name { get; set; }
}

[SerializableType]
class Customer : Person
{
    [SerializableMember(1)]
    public bool IsNewCustomer { get; set; }
}

You may imagine the serializer internally transforming each derived type into a field:

[SerializableType]
class EntityBase
{
    [SerializableMember(1, ValueFormat.Compact)]
    public Person Subtype_Person { get; set; }

    [SerializableMember(2)]
    public int Id { get; set; }
}

[SerializableType]
class Person
{
    [SerializableMember(1, ValueFormat.Compact)]
    public Customer Subtype_Customer { get; set; }

    [SerializableMember(2)]
    public string Name { get; set; }
}

[SerializableType]
class Customer
{
    [SerializableMember(1)]
    public bool IsNewCustomer { get; set; }
}

Therefore subtype tags specified in [SerializeDerivedType] should not intersect with a current type field tags.

It's important to have derived types registered with field numbers because this way each hierarchy has independent subtype tags so you may register different hierarchies in any order. Instead of writing a concrete type index of a type in Model.Types[] the serializer writes a subtype tag which is local for an each base type. Moreover, when a concrete type is not registered in another version the deserializer will still read the most concrete possible type from its hierarchy.

When you don't register derived type explicitly it's registered automatically by an attribute mapper. Read more here.

Clone this wiki locally