I’m trying to determine how to address this use case using protobuf-net (Marc Gravell’s implementation).
- We have class A, which is considered version 1
- An instance of class A has been serialized to disk
- We now have class B, which is considered version 2 of class A (there were so many things wrong with class A, we had to create class B for the next version). Class A still exists in code, but only for legacy purposes.
- I want to deserialize the version:1 data (stored to disk) as a class B instance, and use a logic routine to translate the data from the previous class A instance to a new instance of class B.
- The instance of class B will be serialized to disk during operation.
- The application should expect to deserialize instances of both class A and B.
The concept of data contract surrogates and the DataContractSerializer come to mind. The goal is transition the version:1 data to the new class B structure.
An example:
[DataContract]
public class A {
public A(){}
[DataMember]
public bool IsActive {get;set;]
[DataMember]
public int VersionNumber {
get { return 1; }
set { }
}
[DataMember]
public int TimeInSeconds {get;set;}
[DataMember]
public string Name {get;set;}
[DataMember]
public CustomObject CustomObj {get;set;} //Also a DataContract
[DataMember]
public List<ComplexThing> ComplexThings {get;set;} //Also a DataContract
...
}
[DataContract]
public class B {
public B(A a) {
this.Enabled = a.IsActive; //Property now has a different name
this.TimeInMilliseconds = a.TimeInSeconds * 1000; //Property requires math for correctness
this.Name = a.Name;
this.CustomObject2 = new CustomObject2(a.CustomObj); //Reference objects change, too
this.ComplexThings = new List<ComplexThings>();
this.ComplexThings.AddRange(a.ComplexThings);
...
}
public B(){}
[DataMember]
public bool Enabled {get;set;]
[DataMember]
public int Version {
get { return 2; }
set { }
}
[DataMember]
public double TimeInMilliseconds {get;set;}
[DataMember]
public string Name {get;set;}
[DataMember]
public CustomObject2 CustomObject {get;set;} //Also a DataContract
[DataMember]
public List<ComplexThing> ComplexThings {get;set;} //Also a DataContract
...
}
Class A was the first iteration of our object, and is actively in use. Data exists in v1 format, using class A for serialization.
After realizing the error of our ways, we create a new structure called class B. There are so many changes between A and B that we feel it’s better to create B, as opposed to adapting the original class A.
But our application already exists and class A is being used to serialize data. We’re ready to roll our changes out to the world, but we must first deserialize data created under version 1 (using class A) and instantiate it as class B. The data is significant enough that we can’t just assume defaults in class B for missing data, but rather we must transition the data from a class A instance to class B. Once we have a class B instance, the application will serialize that data again in class B format (version 2).
We’re assuming we’ll make modifications to class B in the future, and we want to be able to iterate to a version 3, perhaps in a new class “C”. We have two goals: address data already in existence, and prepare our objects for future migration.
The existing “transition” attributes (OnSerializing/OnSerialized,OnDeserializing/OnDeserialized,etc.) don’t provide access to the previous data.
What is the expected practice when using protobuf-net in this scenario?
Right; looking at them you have indeed completely changed the contract. I know of no contract-based serializer that is going to love you for that, and protobuf-net is no different. If you already had a root node, you could do something like (in pseudo-code):
and just pick whichever of A/B/C is non-null, perhaps adding some conversion operators between them. However, if you just have a naked A in the old data, this gets hard. There are two approaches I can think of:
Versionas an initial step, and tell the serializer what to expect.For example, you could do:
Now you can use the serializer, telling it what
versionit was serialized as; either via the genericSerializer.Deserialize<T>API, or via aTypeinstance from the two non-generic APIs (Serializer.NonGeneric.DeserializeorRuntimeTypeModel.Default.Deserialize– either way, you get to the same place; it is really a case of whether generic or non-generic is most convenient).Then you would need some conversion code between
A/B/C– either via your own custom operators / methods, or by something like auto-mapper.If you don’t want any
ProtoReadercode kicking around, you could also do:and run
Deserialize<VersionStub>, which will give you access to theVersion, which you can then use to do the type-specific deserialize; the main difference here is that theProtoReadercode allows you to short-circuit as soon as you have a version-number.