.NET 4.6.1 Xml反序列化:根据international属性映射类属性
解决XML反序列化中根据元素属性值映射到不同类属性的问题
针对你遇到的这个需求——把带有international属性的重复XML元素映射到类的不同属性上,.NET的XmlSerializer本身没有直接提供这种“根据属性值路由”的特性,但我们有两种实用的实现方式,下面分别详细说明:
方法一:中间集合+只读属性(简单易上手)
这种思路是先把所有同名的AccountNumber和BankCode元素收集到集合里,再通过标记[XmlIgnore]的只读属性,根据international属性值筛选出对应的值,赋值给你需要的类属性。
1. 定义元素承载类
首先创建两个小类,分别用来捕获AccountNumber和BankCode元素的文本内容以及它们的international属性:
public class AccountNumberElement { // 捕获元素的文本内容 [XmlText] public string Value { get; set; } // 捕获international属性 [XmlAttribute("international")] public bool IsInternational { get; set; } } public class BankCodeElement { [XmlText] public string Value { get; set; } [XmlAttribute("international")] public bool IsInternational { get; set; } }
这里要特别提醒:你的原始类里
AccountNumberInternational定义为long类型,但XML里的国际账户号是DE10000(包含字母),直接用long会反序列化失败,所以我把AccountNumberElement的Value改成了string,后续可以根据需求转换类型。
2. 修改AccountInfo类
在AccountInfo中定义集合属性来接收所有同名元素,再通过只读属性提取对应值:
[XmlRoot("AccountInfo")] public class AccountInfo { [XmlAttribute] public int ID { get; set; } [XmlAttribute] public string Currency { get; set; } // 捕获所有AccountNumber元素 [XmlElement("AccountNumber")] public List<AccountNumberElement> AccountNumbers { get; set; } = new List<AccountNumberElement>(); // 捕获所有BankCode元素 [XmlElement("BankCode")] public List<BankCodeElement> BankCodes { get; set; } = new List<BankCodeElement>(); public string AccountHolder { get; set; } // 非国际账户号(对应international=false) [XmlIgnore] public long AccountNumber { get { var element = AccountNumbers.FirstOrDefault(a => !a.IsInternational); return element != null ? long.Parse(element.Value) : 0; } set { var element = AccountNumbers.FirstOrDefault(a => !a.IsInternational); if (element == null) { element = new AccountNumberElement { IsInternational = false }; AccountNumbers.Add(element); } element.Value = value.ToString(); } } // 国际账户号(对应international=true) [XmlIgnore] public string AccountNumberInternational { get { var element = AccountNumbers.FirstOrDefault(a => a.IsInternational); return element?.Value ?? string.Empty; } set { var element = AccountNumbers.FirstOrDefault(a => a.IsInternational); if (element == null) { element = new AccountNumberElement { IsInternational = true }; AccountNumbers.Add(element); } element.Value = value; } } // 非国际银行代码 [XmlIgnore] public string BankCode { get { var element = BankCodes.FirstOrDefault(b => !b.IsInternational); return element?.Value ?? string.Empty; } set { var element = BankCodes.FirstOrDefault(b => !b.IsInternational); if (element == null) { element = new BankCodeElement { IsInternational = false }; BankCodes.Add(element); } element.Value = value; } } // 国际银行代码 [XmlIgnore] public string BankCodeInternational { get { var element = BankCodes.FirstOrDefault(b => b.IsInternational); return element?.Value ?? string.Empty; } set { var element = BankCodes.FirstOrDefault(b => b.IsInternational); if (element == null) { element = new BankCodeElement { IsInternational = true }; BankCodes.Add(element); } element.Value = value; } } }
使用示例
反序列化代码和常规用法完全一致:
var xml = @"<AccountInfo ID=""accid1"" Currency=""EUR""> <AccountNumber international=""false"">10000</AccountNumber> <AccountNumber international=""true"">DE10000</AccountNumber> <BankCode international=""false"">22222</BankCode> <BankCode international=""true"">BC22222</BankCode> <AccountHolder>Some Dude</AccountHolder> </AccountInfo>"; var serializer = new XmlSerializer(typeof(AccountInfo)); using var reader = new StringReader(xml); var accountInfo = (AccountInfo)serializer.Deserialize(reader); // 访问属性 Console.WriteLine(accountInfo.AccountNumber); // 输出10000 Console.WriteLine(accountInfo.AccountNumberInternational); // 输出DE10000
方法二:实现IXmlSerializable接口(精确控制)
如果需要完全掌控反序列化的每一步,或者不想引入中间集合,可以实现IXmlSerializable接口,手动读取XML节点并赋值到对应的类属性上:
[XmlRoot("AccountInfo")] public class AccountInfo : IXmlSerializable { public int ID { get; set; } public string Currency { get; set; } public long AccountNumber { get; set; } // international=false public string BankCode { get; set; } public string AccountNumberInternational { get; set; } // international=true public string BankCodeInternational { get; set; } public string AccountHolder { get; set; } public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { // 读取根节点的属性 ID = int.Parse(reader.GetAttribute("ID")); Currency = reader.GetAttribute("Currency"); // 移动到第一个子节点 reader.ReadStartElement(); while (!reader.EOF) { if (reader.IsStartElement()) { switch (reader.LocalName) { case "AccountNumber": bool isInternational = bool.Parse(reader.GetAttribute("international")); string accountNumValue = reader.ReadElementContentAsString(); if (isInternational) { AccountNumberInternational = accountNumValue; } else { AccountNumber = long.Parse(accountNumValue); } break; case "BankCode": isInternational = bool.Parse(reader.GetAttribute("international")); string bankCodeValue = reader.ReadElementContentAsString(); if (isInternational) { BankCodeInternational = bankCodeValue; } else { BankCode = bankCodeValue; } break; case "AccountHolder": AccountHolder = reader.ReadElementContentAsString(); break; default: // 跳过未知节点,避免反序列化失败 reader.Skip(); break; } } else { // 移动到下一个节点 reader.Read(); } } } public void WriteXml(XmlWriter writer) { // 写入根节点属性 writer.WriteAttributeString("ID", ID.ToString()); writer.WriteAttributeString("Currency", Currency); // 写入非国际账户号 writer.WriteStartElement("AccountNumber"); writer.WriteAttributeString("international", "false"); writer.WriteString(AccountNumber.ToString()); writer.WriteEndElement(); // 写入国际账户号 writer.WriteStartElement("AccountNumber"); writer.WriteAttributeString("international", "true"); writer.WriteString(AccountNumberInternational); writer.WriteEndElement(); // 写入非国际银行代码 writer.WriteStartElement("BankCode"); writer.WriteAttributeString("international", "false"); writer.WriteString(BankCode); writer.WriteEndElement(); // 写入国际银行代码 writer.WriteStartElement("BankCode"); writer.WriteAttributeString("international", "true"); writer.WriteString(BankCodeInternational); writer.WriteEndElement(); // 写入账户持有人 writer.WriteElementString("AccountHolder", AccountHolder); } }
这种方法的优势是完全自定义读写逻辑,适合复杂的XML结构,但需要自己处理所有节点的解析和生成,代码量会多一些。
内容的提问来源于stack exchange,提问作者Kris
相关产品推荐
相关产品推荐

