C#根据XML节点id属性反序列化不同类型data节点的方案
核心原因
XmlSerializer 默认不会根据XML属性值自动匹配派生类型,它识别派生类的唯一标识是节点上的xsi:type属性。你的XML中没有该标识,因此所有row节点都会被反序列化为基类row的实例,派生类定义的属性自然无法被赋值,出现数据丢失。
xsd工具生成合并属性的通用类,是因为XSD语法本身无法描述「父节点id属性值决定子节点结构」这类关联约束,只能将所有可能出现的属性合并到同一个类中。
实现方案
提供两种可直接运行的实现方式,可根据场景选择:
方案1:自定义反序列化逻辑,直接得到目标类结构
通过实现IXmlSerializable接口自定义data节点的解析规则,完全匹配期望的类结构,不需要反序列化后做二次转换。
类定义代码如下:
using System; using System.Collections.Generic; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; public class document : IXmlSerializable { public UserInfoRow[] dataUserInfos { get; set; } public UserTransactionRow[] dataUserTransactions { get; set; } public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { var infoRows = new List<UserInfoRow>(); var transRows = new List<UserTransactionRow>(); reader.ReadStartElement("document"); while (reader.NodeType != XmlNodeType.EndElement) { if (reader.LocalName != "data") { reader.Skip(); continue; } string dataType = reader.GetAttribute("id"); reader.ReadStartElement("data"); if (reader.LocalName == "rows") { reader.ReadStartElement("rows"); while (reader.LocalName == "row") { using var rowReader = reader.ReadSubtree(); switch (dataType) { case "UserInfo": var infoSerializer = new XmlSerializer(typeof(UserInfoRow)); infoRows.Add((UserInfoRow)infoSerializer.Deserialize(rowReader)); break; case "UserTransactions": var transSerializer = new XmlSerializer(typeof(UserTransactionRow)); transRows.Add((UserTransactionRow)transSerializer.Deserialize(rowReader)); break; default: reader.Skip(); break; } reader.Read(); } reader.ReadEndElement(); } reader.ReadEndElement(); } reader.ReadEndElement(); dataUserInfos = infoRows.ToArray(); dataUserTransactions = transRows.ToArray(); } public void WriteXml(XmlWriter writer) { // 序列化逻辑可按需实现 writer.WriteStartElement("data"); writer.WriteAttributeString("id", "UserInfo"); writer.WriteStartElement("rows"); var infoSerializer = new XmlSerializer(typeof(UserInfoRow)); foreach (var row in dataUserInfos ?? Array.Empty<UserInfoRow>()) { infoSerializer.Serialize(writer, row); } writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteStartElement("data"); writer.WriteAttributeString("id", "UserTransactions"); writer.WriteStartElement("rows"); var transSerializer = new XmlSerializer(typeof(UserTransactionRow)); foreach (var row in dataUserTransactions ?? Array.Empty<UserTransactionRow>()) { transSerializer.Serialize(writer, row); } writer.WriteEndElement(); writer.WriteEndElement(); } } public class UserInfoRow { [XmlAttribute("id")] public string id { get; set; } [XmlAttribute("Name")] public string Name { get; set; } [XmlAttribute("phone")] public string phone { get; set; } } public class UserTransactionRow { [XmlAttribute("UserId")] public string UserId { get; set; } [XmlAttribute("amount")] public decimal amount { get; set; } [XmlAttribute("date")] public DateTime date { get; set; } }
调用方式和标准XmlSerializer完全一致:
using var stream = File.OpenRead("target.xml"); var serializer = new XmlSerializer(typeof(document)); var result = (document)serializer.Deserialize(stream);
方案2:保留中间类结构,反序列化后手动映射
如果不想自定义序列化接口,可以保留通用中间类做自动反序列化,完成后根据data节点的id属性,手动将行数据映射为对应强类型。
注意不要使用基类+XmlInclude的方案,该方案依赖XML中的xsi:type属性,在当前场景下无法生效。可以将行节点先反序列化为XmlElement或者通用属性字典,再做映射。
注意:
XmlArrayItem特性仅支持根据XML元素名区分派生类型,无法根据父节点的属性值做类型判断,不要在该场景下调试该配置。
内容的提问来源于stack exchange,提问作者Actimele
相关产品推荐
相关产品推荐

