dvjanm
dvjanm

Reputation: 2381

Error deserializing XML with DataContractSerializer

I am deserializing xml to objects with this method:

public T Deserialize(string filename)
{
    var xml = File.ReadAllText(filename);
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(xml);
    writer.Flush();
    stream.Position = 0;
    DataContractSerializer dcs = new DataContractSerializer(typeof(T));
    T obj = (T)dcs.ReadObject(stream);
    return obj;
}

I have an old XML, and since then some properties added/removed from the a class I am serializing/deserializing.

I have the following exception:

Deserialized object with reference id 'i53' not found in stream.

Is it possible to customize the DataContractSerializer to just skip a property if it is not in the model anymore? Note that the deleted property is a reference to another complex object, not to simple type. The XML file contains that, my class not anymore.

Upvotes: 1

Views: 2395

Answers (1)

dbc
dbc

Reputation: 116516

The exception message Deserialized object with reference id 'i53' not found in stream can get thrown when you enable the data contract serializer's object reference preservation functionality. It indicates that, during deserialization, a reference to an undefined object was encountered, and so cannot be deserialized.

I was able to reproduce the problem by obsoleting a data member as follows. First, I defined the following types:

namespace V1
{
    [DataContract(Name = "Member", Namespace = "Question45008433", IsReference = true)]
    public class Member
    {
        [DataMember]
        public string Name { get; set; }
    }

    [DataContract(Name = "Root", Namespace = "Question45008433")]
    public class RootObject
    {
        [DataMember(Order = 1)]
        public Member MainMember { get; set; }

        [DataMember(Order = 2)]
        public List<Member> Members { get; set; }
    }
}

Then I created a test object as follows:

var list = new List<V1.Member> { new V1.Member { Name = "Foo" }, new V1.Member { Name = "Bar" } };
var v1 = new V1.RootObject { MainMember = list[0], Members = list };

Notice that the Foo object is referred to twice, once from MainMember and once from the Members list.

When I serialized this using DataContractSerializer, I got the following XML:

<Root xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Question45008433">
  <MainMember z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Name>Foo</Name>
  </MainMember>
  <Members>
    <Member z:Ref="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" />
    <Member z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
      <Name>Bar</Name>
    </Member>
  </Members>
</Root>

Notice that the Foo object is fully serialized when it is first serialized as <MainMember>, whereupon it is given a z:Id="i1" attribute. When subsequent references are encountered during serialization, only a reference is serialized via z:Ref="i1".

Next, I decided that the MainMember data member was unnecessary and obsoleted it:

namespace V2
{
    [DataContract(Name = "Member", Namespace = "Question45008433", IsReference = true)]
    public class Member
    {
        [DataMember]
        public string Name { get; set; }
    }

    [DataContract(Name = "Root", Namespace = "Question45008433")]
    public class RootObject
    {
        [DataMember(Order = 2)]
        public List<Member> Members { get; set; }
    }
}

Now, if I try to deserialize the original XML using this modified contract, I get the very exception you are seeing:

System.Runtime.Serialization.SerializationException: Deserialized object with reference id 'i1' not found in stream.
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.GetExistingObject(String id, Type type, String name, String ns)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.TryHandleNullOrRef(XmlReaderDelegator reader, Type declaredType, String name, String ns, Object& retObj)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, DataContract& dataContract)

Why is this happening? It occurs because the obsoleted data member came before the remaining data members. Thus, during deserialization, the defining element is skipped and ignored, and the subsequent references cannot get resolved.

The workaround is to add back the original data member as a private fake synthetic property that does nothing and always returns null:

namespace V3
{
    [DataContract(Name = "Member", Namespace = "Question45008433", IsReference = true)]
    public class Member
    {
        [DataMember]
        public string Name { get; set; }
    }

    [DataContract(Name = "Root", Namespace = "Question45008433")]
    public class RootObject
    {
        [DataMember(EmitDefaultValue = false, Order = 1)]
        Member MainMember
        {
            get
            {
                return null;
            }
            set
            {
                // Do nothing
            }
        }


        [DataMember(Order = 2)]
        public List<Member> Members { get; set; }
    }
}

The original XML can now be deserialized successfully because, during deserialization, the data contract serializer itself maintains a lookup table of all reference elements by name. However, the z:Ref="i1" element only gets added when encountered if it corresponds to a currently valid member. And, because EmitDefaultValue = false, the obsoleted element will no longer appear when serialized.

Upvotes: 1

Related Questions