user1546077
user1546077

Reputation: 167

Deserialize property to abstract base reference

Is it possible to deserialize an aggregate type that has a property to an abstract base type that is a reference, see Aggregate.Base? If not, what is the best workaround?

[ProtoContract]
[ProtoInclude(1, typeof(Derived))]
public abstract class Base { }

[ProtoContract]
public class Derived : Base
{
    [ProtoMember(1)]
    public int SomeProperty { get; set; }
}

[ProtoContract]
public class Aggregate
{
    [ProtoMember(1, AsReference = true)]
    public Base Base { get; set; }
}

[TestClass]
public class UnitTest
{
    [TestMethod]
    public void TestMethod1()
    {
        var value = new Aggregate { Base = new Derived() };
        using (var stream = new MemoryStream())
        {
            Serializer.Serialize(stream, value);
            stream.Position = 0;

            // Raises an exception
            // Unable to create type Sage.Estimating.Data.Base: Cannot create an abstract class.
            Serializer.Deserialize<Aggregate>(stream);
        }
    }
}

Call stack at the point the exception is raised:

protobuf-net.dll!ProtoBuf.BclHelpers.ReadNetObject(object value, ProtoBuf.ProtoReader source, int key, System.Type type, ProtoBuf.BclHelpers.NetObjectOptions options) Line 428 + 0xda bytes C# protobuf-net.dll!ProtoBuf.Serializers.NetObjectSerializer.Read(object value, ProtoBuf.ProtoReader source) Line 45 + 0x9f bytes C# protobuf-net.dll!ProtoBuf.Serializers.TagDecorator.Read(object value, ProtoBuf.ProtoReader source) Line 66 + 0x18 bytes C# protobuf-net.dll!ProtoBuf.Serializers.PropertyDecorator.Read(object value, ProtoBuf.ProtoReader source) Line 74 + 0x18 bytes C# protobuf-net.dll!ProtoBuf.Serializers.TypeSerializer.Read(object value, ProtoBuf.ProtoReader source) Line 205 + 0xf bytes C# protobuf-net.dll!ProtoBuf.Meta.RuntimeTypeModel.Deserialize(int key, object value, ProtoBuf.ProtoReader source) Line 562 + 0xf bytes C# protobuf-net.dll!ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoBuf.ProtoReader reader, System.Type type, object value, bool noAutoCreate) Line 634 + 0x14 bytes C# protobuf-net.dll!ProtoBuf.Meta.TypeModel.Deserialize(System.IO.Stream source, object value, System.Type type, ProtoBuf.SerializationContext context) Line 555 + 0x14 bytes C# protobuf-net.dll!ProtoBuf.Meta.TypeModel.Deserialize(System.IO.Stream source, object value, System.Type type) Line 534 + 0x13 bytes C# protobuf-net.dll!ProtoBuf.Serializer.Deserialize(System.IO.Stream source) Line 78 + 0x5a bytes C#

Upvotes: 2

Views: 929

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1064324

Thank you for an excellent scenario; not sure how I overlooked that. Basically, it comes down to key tracking, which gets particularly complex during cyclic graphs. In order to get the key registered ASAP, what it used to do was (for new objects):

  • create an instance of the object
  • register it against the known key
  • deserialize the payload into the newly created object

Obviously the first step is an error in the case of inheritance, regardless of whether the base-type is abstract / non-creatable. What it does now is:

  • register a dummy (non-fetchable) value against the known key
  • deserialize the payload starting from null
  • trap the object (update the dummy) as it gets created (this code pretty much already existed, although it was specialised for the "root object" scenario; now made more generic)

The upshot of this is: it now works; your test passes, and the object is of the correct type:

var obj = Serializer.Deserialize<Aggregate>(stream);
Assert.AreEqual(typeof(Derived), obj.Base.GetType());

Needs revision 556 or later.

Upvotes: 2

Related Questions