如何在C#中用XmlSerializer反序列化同名不同类型的XML元素
嘿,这个场景我之前做项目时遇到过,其实核心是把那些带编号的MealPeriodN和MealPeriodNSales配对成逻辑上的对象,咱们分两种情况来解决:
情况1:时段数量固定(比如已知最多3个)
如果你的XML里的时段数量是固定的(比如永远只有1-3个),可以直接通过XmlElement特性来映射每个元素,再做一个只读属性把它们分组:
首先定义一个用来承载单时段数据的类:
public class MealPeriodSales { public string Name { get; set; } public decimal Amount { get; set; } }
然后定义SalesSummary类,用XmlElement指定每个XML元素的名称:
[XmlRoot("SalesSummary")] public class SalesSummary { [XmlElement("MealPeriod1")] public string MealPeriod1Name { get; set; } [XmlElement("MealPeriod1Sales")] public decimal MealPeriod1Amount { get; set; } [XmlElement("MealPeriod2")] public string MealPeriod2Name { get; set; } [XmlElement("MealPeriod2Sales")] public decimal MealPeriod2Amount { get; set; } [XmlElement("MealPeriod3")] public string MealPeriod3Name { get; set; } [XmlElement("MealPeriod3Sales")] public decimal MealPeriod3Amount { get; set; } // 只读属性,把分散的字段整合成列表,用[XmlIgnore]避免序列化时被处理 [XmlIgnore] public List<MealPeriodSales> MealPeriods { get { var periods = new List<MealPeriodSales>(); if (!string.IsNullOrEmpty(MealPeriod1Name)) periods.Add(new MealPeriodSales { Name = MealPeriod1Name, Amount = MealPeriod1Amount }); if (!string.IsNullOrEmpty(MealPeriod2Name)) periods.Add(new MealPeriodSales { Name = MealPeriod2Name, Amount = MealPeriod2Amount }); if (!string.IsNullOrEmpty(MealPeriod3Name)) periods.Add(new MealPeriodSales { Name = MealPeriod3Name, Amount = MealPeriod3Amount }); return periods; } } }
最后反序列化的代码:
// 假设xmlContent是你的XML字符串 using (var reader = new StringReader(xmlContent)) { var serializer = new XmlSerializer(typeof(SalesSummary)); var summary = (SalesSummary)serializer.Deserialize(reader); // 这里summary.MealPeriods就拿到了所有配对好的时段数据 }
情况2:时段数量不固定(可能有N个)
如果XML里的时段数量是动态的(比如可能有MealPeriod4、MealPeriod5...),那上面的方法就不适用了,这时候可以通过实现IXmlSerializable接口来自定义序列化逻辑:
首先还是定义MealPeriodSales类,然后让SalesSummary实现IXmlSerializable:
using System.Globalization; using System.Xml; using System.Xml.Serialization; [XmlRoot("SalesSummary")] public class SalesSummary : IXmlSerializable { public List<MealPeriodSales> MealPeriods { get; set; } = new List<MealPeriodSales>(); // 这个方法返回null即可,不需要自定义Schema public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { // 先读取根节点 reader.ReadStartElement("SalesSummary"); // 遍历所有子节点 while (reader.NodeType != XmlNodeType.EndElement) { // 判断当前节点是时段名称(不带Sales后缀) if (reader.Name.StartsWith("MealPeriod") && !reader.Name.EndsWith("Sales")) { // 读取时段名称 var periodName = reader.ReadElementContentAsString(); // 接下来应该是对应的Sales节点,检查节点名称是否匹配 var expectedSalesNodeName = $"{reader.Name.Substring(0, reader.Name.LastIndexOf('d') + 1)}Sales"; if (reader.Name == expectedSalesNodeName) { // 读取销售额,用InvariantCulture避免区域格式问题 var salesAmount = decimal.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture); MealPeriods.Add(new MealPeriodSales { Name = periodName, Amount = salesAmount }); } else { // 如果没有对应的Sales节点,跳过当前节点 reader.Skip(); } } else { // 跳过非目标节点 reader.Skip(); } } // 读取根节点的结束标签 reader.ReadEndElement(); } // 如果需要把对象序列化回XML,可以实现这个方法 public void WriteXml(XmlWriter writer) { for (int i = 0; i < MealPeriods.Count; i++) { var period = MealPeriods[i]; writer.WriteElementString($"MealPeriod{i+1}", period.Name); writer.WriteElementString($"MealPeriod{i+1}Sales", period.Amount.ToString("F2", CultureInfo.InvariantCulture)); } } } public class MealPeriodSales { public string Name { get; set; } public decimal Amount { get; set; } }
反序列化代码和之前一样,这样不管XML里有多少个时段,都能自动配对并加载到MealPeriods列表里。
内容的提问来源于stack exchange,提问作者Justin Lutsky
相关产品推荐
相关产品推荐

