使用 DataContractSerializer 反序列化 XML 时出错

Posted

技术标签:

【中文标题】使用 DataContractSerializer 反序列化 XML 时出错【英文标题】:Error deserializing XML with DataContractSerializer 【发布时间】: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 文件包含它,我的类不再包含。

【问题讨论】:

How can I ignore a property when serializing using the DataContractSerializer?的可能重复 @WimOmbelets 请阅读这两个问题。他们是两个不同的话题…… 我们需要看到您的问题的minimal reproducible example。听起来object reference tracking 出了点问题。例如,您可能废弃了定义对象的数据成员,因此稍后通过 "z:Ref" 属性在 XML 中引用该对象失败。 @dbc 你是对的,我没有在问题中提到,deleted 属性是对另一个复杂对象的引用,而不是简单类型。 XML 文件包含它,我的课程不再包含。 在类中将属性设为私有会从 xml 中排除属性。但是反序列化时会出错。发生错误是因为 xml 包含不在类中的属性。因此,如果您的某些 xml 文件包含标签,那么它必须在一个类中。 【参考方案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,序列化的时候不再出现被废弃的元素。

【讨论】:

感谢您的努力,您完美地重现并解决了我什至没有正确描述的问题。 如果反序列化时发现额外的元素,我们可以跳过吗?我的意思是,即使序列化内容中存在额外的成员,我们也应该始终能够反序列化为我们想要的内容,对吧?

以上是关于使用 DataContractSerializer 反序列化 XML 时出错的主要内容,如果未能解决你的问题,请参考以下文章

使用 DataContractSerializer 时设置属性的初始值

使用 DataContractSerializer 和 XmlDictionaryWriter 序列化 JObject 后崩溃

在原语列表上使用 DataContractSerializer 的自定义元素名称

使用 DataContractSerializer 的接口中的显式类型

使用 DataContractSerializer 序列化没有命名空间的对象

如何使用 DataContractSerializer 从 XMLDocument 的单个节点反序列化?