Backward Compatibility Protobuf-net

I am trying to add a new enum value for a specific protobuf serial class class in a new version of the application, and when testing, I noticed that the previous version throws an exception, given this new file format:

  An unhandled exception of type 'ProtoBuf.ProtoException' occurred in protobuf-net.dll
 Additional information: No {enum-type-name} enum is mapped to the wire-value 3

It is pretty obvious that he tells me that there is no enum value for the int 3 value, but I always thought that the protocol buffers default to the null value ("default") of the enumeration (if one exists), in case the actual enumeration value is not can be matched.

To clarify, this can be reproduced in the following example (I deliberately take the step of deserializing to another class to mimic an old application trying to load a new format):

 // --- version 1 --- public enum EnumV1 { Default = 0, One = 1, Two = 2 } [ProtoContract] public class ClassV1 { [ProtoMember(1)] public EnumV1 Value { get; set; } } // --- version 2 --- public enum EnumV2 { Default = 0, One = 1, Two = 2, Three = 3 // <- newly added } [ProtoContract] public class ClassV2 { [ProtoMember(1)] public EnumV2 Value { get; set; } } 

And the following code will not be executed:

 // serialize v2 using the new app var v2 = new ClassV2() { Value = EnumV2.Three }; var v2data = Serialize(v2); // try to deserialize this inside the old app to v1 var v1roundtrip = Deserialize<ClassV1>(v2data); 

Since v1 is in the open state, is there some metadata that I can use when serializing in v2 to avoid this problem? Of course, I can get rid of this problem by rewriting v2 to use a separate property and leave the enumeration values ​​unmodified, but I would like to make the transitions backward compatible, if possible.

+6
source share
4 answers

Since v1 is in the open state, is there some metadata that I can use when serializing in v2 to avoid this problem? Of course, I can get rid of this problem by rewriting v2 to use a separate property and leave the enumeration values ​​unmodified, but I would like to make the transitions backward compatible, if possible.

What you are experiencing is the protobuf-net error described here protobuf-net - issue # 422: Invalid behavior on deserialization unknown value enum .

It seems that it has not yet been fixed according to the exception of the erroneous enumeration protobuf-net (problem 422) here needs a good workaround (and, of course, your post).

Unfortunately, you need to either fix the protobuf-net source code or use workarounds.

UPDATE: I checked the code in the GitHub repository and confirmed that the problem is still not fixed. Here is the problematic code inside EnumSerializer.cs ( ISSUE #422 comment belongs to me):

 public object Read(object value, ProtoReader source) { Helpers.DebugAssert(value == null); // since replaces int wireValue = source.ReadInt32(); if(map == null) { return WireToEnum(wireValue); } for(int i = 0 ; i < map.Length ; i++) { if(map[i].WireValue == wireValue) { return map[i].TypedValue; } } // ISSUE #422 source.ThrowEnumException(ExpectedType, wireValue); return null; // to make compiler happy } 
+1
source

Adding [ProtoContract(EnumPassthru=true)] to your listings will allow protobuf-net deserialize unknown values.

Unfortunately, there is no way to retroactively fix your v1. You will have to use another property.

+3
source

Your ClassV1 lacks advanced compatibility.

I would execute the Proto contract so that it serializes / deserializes the string representation of the enum value. This way you can handle the fallback default value yourself. The Value property will not serialize / deserialize.

 public enum EnumV1 { Default = 0, One = 1, Two = 2 } public enum EnumV2 { Default = 0, One = 1, Two = 2, Three = 3 // <- newly added } [ProtoContract] public class ClassV1 { [ProtoMember(1)] public string ValueAsString { get { return Value.ToString(); } set { try { Value = (EnumV1) Enum.Parse(typeof (EnumV1), value); } catch (Exception) { Value = EnumV1.Default; } } } public EnumV1 Value { get; set; } } [ProtoContract] public class ClassV2 { [ProtoMember(1)] public string ValueAsString { get { return Value.ToString(); } set { try { Value = (EnumV2)Enum.Parse(typeof(EnumV2), value); } catch (Exception) { Value = EnumV2.Default; } } } public EnumV2 Value { get; set; } } 

However, this does not solve the problem that there should be a non-intercept-compatible class in production.

0
source

You can add the DefaultValue attribute to the proto member property.

 [ProtoContract] public class ClassV1 { [ProtoMember(1), DefaultValue(EnumV1.Default)] public EnumV1 Value { get; set; } } 

To understand how a property should be initialized for a default case.

0
source

All Articles