【问题标题】:Error deserializing XML with DataContractSerializer使用 DataContractSerializer 反序列化 XML 时出错
【发布时间】:2017-07-10 09:30:31
【问题描述】:

我正在使用这种方法将 xml 反序列化为对象:

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;
}

我有一个旧的 XML,从那时起我正在序列化/反序列化的类中添加/删除了一些属性。

我有以下异常:

在流中找不到引用 id 为“i53”的反序列化对象。

是否可以自定义 DataContractSerializer 以跳过模型中不再存在的属性?请注意,deleted 属性是对另一个复杂对象的引用,而不是对简单类型的引用。 XML 文件包含它,我的类不再包含。

【问题讨论】:

  • @WimOmbelets 请阅读这两个问题。他们是两个不同的话题……
  • 我们需要看到您的问题的minimal reproducible example。听起来object reference tracking 出了点问题。例如,您可能废弃了定义对象的数据成员,因此稍后通过 "z:Ref" 属性在 XML 中引用该对象失败。
  • @dbc 你是对的,我没有在问题中提到,deleted 属性是对另一个复杂对象的引用,而不是简单类型。 XML 文件包含它,我的课程不再包含。
  • 在类中将属性设为私有会从 xml 中排除属性。但是反序列化时会出错。发生错误是因为 xml 包含不在类中的属性。因此,如果您的某些 xml 文件包含标签,那么它必须在一个类中。

标签: c# xml serialization datacontractserializer


【解决方案1】:

当您启用数据协定序列化程序的 object reference preservation 功能时,可能会抛出异常消息 Deserialized object with reference id 'i53' not found in stream。表示在反序列化过程中遇到了对未定义对象的引用,因此无法反序列化。

我能够通过如下废弃数据成员来重现该问题。首先,我定义了以下类型:

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; }
    }
}

然后我创建了一个测试对象如下:

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 };

注意Foo 对象被引用了两次,一次来自MainMember,一次来自Members 列表。

当我使用DataContractSerializer 对其进行序列化时,我得到了以下 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>

注意Foo 对象在第一次序列化&lt;MainMember&gt; 时被完全序列化,因此它被赋予z:Id="i1" 属性。序列化过程中遇到后续引用时,仅通过z:Ref="i1"序列化一个引用。

接下来,我认为 MainMember 数据成员是不必要的并已废弃:

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; }
    }
}

现在,如果我尝试使用此修改后的合约反序列化原始 XML,我会得到您所看到的异常:

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)

为什么会这样?发生这种情况是因为过时的数据成员其余数据成员之前出现。因此,在反序列化过程中,定义元素被跳过并忽略,后续引用无法解析。

解决方法是将原始数据成员添加为私有假合成属性,该属性不执行任何操作并始终返回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; }
    }
}

现在可以成功反序列化原始 XML,因为在反序列化期间,数据协定序列化程序本身会按名称维护所有引用元素的查找表。但是,z:Ref="i1" 元素只有在遇到当前有效成员时才会被添加。并且,因为EmitDefaultValue = false,序列化的时候不再出现被废弃的元素。

【讨论】:

  • 感谢您的努力,您完美地重现并解决了我什至没有正确描述的问题。
  • 如果反序列化时发现额外的元素,我们可以跳过吗?我的意思是,即使序列化内容中存在额外的成员,我们也应该始终能够反序列化为我们想要的内容,对吧?
猜你喜欢
  • 1970-01-01
  • 2013-03-03
  • 2021-02-13
  • 2013-06-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-31
  • 2018-07-05
  • 2017-09-25
相关资源
最近更新 更多