使用 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
对象在第一次序列化为<MainMember>
时被完全序列化,因此它被赋予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 的接口中的显式类型