-
Notifications
You must be signed in to change notification settings - Fork 3
Versioning and field numbers
Often it's required to be able to serialize objects in one version of an application and deserialize in another.
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 = ...)]
.
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.