c# 使用 IXmlSerializable 反序列化
c# Deserialize with IXmlSerializable
我有 xml 这样的:
<data>
<audit_values>
<audit_value>
<channel>2</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
<audit_value>
<channel>1</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
</audit_values>
</data>
我需要将它反序列化为 class。 但问题是,那一周以后会改变(它将包含更多元素,我不知道它们的名称)
数据:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
审计值:
[XmlRoot("audit_value")]
public class AuditValue
{
[XmlElement("week", typeof(TVR))]
public Week Week;
}
周:
[XmlRoot("week")]
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.Read();
var sub = reader.ReadSubtree();
do
{
if (sub.NodeType == XmlNodeType.Element)
{
string name = sub.Name;
string val = sub.ReadElementContentAsString();
Values.Add(name, Convert.ToDouble(val));
}
} while (sub.Read());
}
public void WriteXml(XmlWriter writer)
{
}
}
但是在反序列化之后,我只有一个元素,其中只有一个元素被记录在字典值中。我做错了什么?
GitHub:
https://github.com/olegletynain/XmlTest/tree/master/XmlTestRead
试试这个。 XmlElement 消除了一些您没有的标签。您在值的末尾有一个额外的 's'。
From
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
To
[XmlElement("audit_value")]
public AuditValue[] AuditValues { get; set; }
您应该考虑使用 DataContractSerializer 而不是 XmlSerializer 并在您的 DataContract 上实现 IExtensibleDataObject 接口 class。
实现 IExtensibleDataObject 允许 DataContract class 将未知信息保存在 ExtensionData 字段中,这可以防止如果被反序列化的 XML 包含未知元素,然后重新序列化和已保存。
您的审计 class 看起来像这样:
[DataContract(Name = "audit_value", Namespace = "")]
public class AuditValue : IExtensibleDataObject
{
[DataMember(Name = "channel")]
public int Channel { get; set; }
[DataMember(Name = "week")]
public Week Week { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "week", Namespace = "")]
public class Week : IExtensibleDataObject
{
[DataMember(Name = "mo_th")]
public Decimal MondayThroughThursday { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
您仍然需要更新您的代码以将附加元素反序列化为 POCO,但至少底层数据将被保留,直到您开始使用它。
我根据@Matthew Whited 在评论部分的想法调整了您的 ReadXml 方法,以下方法可以完成工作:
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
作为旁注,您只需要在实际的根元素上使用 XmlRoot,而不是在每个 class 上,所以我将其从 AuditValue 和 Week 中删除。我也不知道TVR是什么。它没有用 "typeof(TVR)" 编译,所以我也删除了它。
为了完整起见,这是我对 classes 的版本:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
public class AuditValue
{
[XmlElement("week")]
public Week Week;
}
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
public void WriteXml(XmlWriter writer)
{
}
}
错误在ReadXml方法中,现在我将其更改为:
public void ReadXml(XmlReader reader)
{
reader.Read();
do
{
if (!reader.IsEmptyElement)
{
var name = reader.Name;
var val = Convert.ToDouble(reader.ReadElementContentAsString());
Values.Add(name, val);
}
else
{
reader.Skip();
}
} while (reader.Name != "week");
if (reader.NodeType == XmlNodeType.EndElement)
{
reader.ReadEndElement();
}
}
而且我工作得很好。此解决方案不使用 XElement 和 Linq,@Volkan Paksoy 提供了更易于理解的 XElement 方法
我有 xml 这样的:
<data>
<audit_values>
<audit_value>
<channel>2</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
<audit_value>
<channel>1</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
</audit_values>
</data>
我需要将它反序列化为 class。 但问题是,那一周以后会改变(它将包含更多元素,我不知道它们的名称)
数据:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
审计值:
[XmlRoot("audit_value")]
public class AuditValue
{
[XmlElement("week", typeof(TVR))]
public Week Week;
}
周:
[XmlRoot("week")]
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.Read();
var sub = reader.ReadSubtree();
do
{
if (sub.NodeType == XmlNodeType.Element)
{
string name = sub.Name;
string val = sub.ReadElementContentAsString();
Values.Add(name, Convert.ToDouble(val));
}
} while (sub.Read());
}
public void WriteXml(XmlWriter writer)
{
}
}
但是在反序列化之后,我只有一个元素,其中只有一个元素被记录在字典值中。我做错了什么?
GitHub: https://github.com/olegletynain/XmlTest/tree/master/XmlTestRead
试试这个。 XmlElement 消除了一些您没有的标签。您在值的末尾有一个额外的 's'。
From
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
To
[XmlElement("audit_value")]
public AuditValue[] AuditValues { get; set; }
您应该考虑使用 DataContractSerializer 而不是 XmlSerializer 并在您的 DataContract 上实现 IExtensibleDataObject 接口 class。
实现 IExtensibleDataObject 允许 DataContract class 将未知信息保存在 ExtensionData 字段中,这可以防止如果被反序列化的 XML 包含未知元素,然后重新序列化和已保存。
您的审计 class 看起来像这样:
[DataContract(Name = "audit_value", Namespace = "")]
public class AuditValue : IExtensibleDataObject
{
[DataMember(Name = "channel")]
public int Channel { get; set; }
[DataMember(Name = "week")]
public Week Week { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "week", Namespace = "")]
public class Week : IExtensibleDataObject
{
[DataMember(Name = "mo_th")]
public Decimal MondayThroughThursday { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
您仍然需要更新您的代码以将附加元素反序列化为 POCO,但至少底层数据将被保留,直到您开始使用它。
我根据@Matthew Whited 在评论部分的想法调整了您的 ReadXml 方法,以下方法可以完成工作:
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
作为旁注,您只需要在实际的根元素上使用 XmlRoot,而不是在每个 class 上,所以我将其从 AuditValue 和 Week 中删除。我也不知道TVR是什么。它没有用 "typeof(TVR)" 编译,所以我也删除了它。
为了完整起见,这是我对 classes 的版本:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
public class AuditValue
{
[XmlElement("week")]
public Week Week;
}
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
public void WriteXml(XmlWriter writer)
{
}
}
错误在ReadXml方法中,现在我将其更改为:
public void ReadXml(XmlReader reader)
{
reader.Read();
do
{
if (!reader.IsEmptyElement)
{
var name = reader.Name;
var val = Convert.ToDouble(reader.ReadElementContentAsString());
Values.Add(name, val);
}
else
{
reader.Skip();
}
} while (reader.Name != "week");
if (reader.NodeType == XmlNodeType.EndElement)
{
reader.ReadEndElement();
}
}
而且我工作得很好。此解决方案不使用 XElement 和 Linq,@Volkan Paksoy 提供了更易于理解的 XElement 方法