C#对象序列化为XML生成重复数组的问题排查与解决
XML序列化列表重复生成父节点问题及驼峰命名解决方案
问题场景
需要将包含packages列表的TestRequest类序列化为XML发送给API,期望生成的XML结构为:
<testRequest> <packages> <package> <id>1</id> </package> <package> <id>2</id> </package> </packages> </testRequest>
但实际序列化后,每个package都对应一个独立的<packages>节点:
<TestRequest> <packages> <package> <id>1</id> </package> </packages> <packages> <package> <id>2</id> </package> </packages> </TestRequest>
相关类定义、序列化方法及对象构建代码如下:
[System.SerializableAttribute()] public class TestRequest { [XmlElement(ElementName = "packages")] public List<PackageRoot>? Packages { get; set; } } [System.SerializableAttribute()] public partial class PackageRoot { [XmlElement(ElementName = "package")] public MyPackage? Package { get; set; } } [System.SerializableAttribute()] public partial class MyPackage { [XmlElement(ElementName = "id")] public string? Id { get; set; } } // 序列化方法 private XmlDocument ConvertToXml(object o) { var ser = new XmlSerializer(o.GetType()); StringWriter writer = new StringWriter(); ser.Serialize(writer, o); var xml = writer.ToString(); writer.Close(); var doc = new XmlDocument(); doc.LoadXml(xml); return doc; } // 对象构建代码 var testRequest = new TestRequest() { Packages = [] }; var p1 = new MyPackage() { Id = "1" }; var p2 = new MyPackage() { Id = "2" }; testRequest.Packages.Add(new PackageRoot() { Package = p1 }); testRequest.Packages.Add(new PackageRoot() { Package = p2 }); var xmltestlRequest = ConvertToXml(testRequest);
未添加[XmlElement(ElementName = "xxx")]特性时,序列化结构正常,添加特性是为了实现XML节点的驼峰命名,但导致了列表结构异常。
问题原因
给List<T>类型的属性标记[XmlElement]特性时,XmlSerializer会将列表中的每个元素都序列化为独立的同名节点,而非将整个列表包裹在一个父节点下。
默认情况下,XmlSerializer对列表类型会采用[XmlArray](生成包裹列表的父节点)和[XmlArrayItem](定义列表子项的节点名)的行为,添加[XmlElement]特性后覆盖了这个默认逻辑,导致每个PackageRoot实例都生成一个<packages>节点。
解决方案
方案1:修正特性配置(特性方式实现驼峰)
如果想继续用特性控制命名,需要调整特性类型:
- 将
TestRequest中Packages属性的[XmlElement]改为[XmlArray(ElementName = "packages")],指定包裹列表的父节点名 - 将
PackageRoot中Package属性添加[XmlArrayItem(ElementName = "package")],指定列表子项的节点名
修正后的类定义(可简化中间类):
[System.SerializableAttribute()] public class TestRequest { [XmlArray(ElementName = "packages")] [XmlArrayItem(ElementName = "package")] public List<MyPackage>? Packages { get; set; } } [System.SerializableAttribute()] public partial class MyPackage { [XmlElement(ElementName = "id")] public string? Id { get; set; } }
这样序列化后会生成符合预期的XML结构,同时实现驼峰命名。
方案2:移除特性+正则转换驼峰(最终采用方案)
移除所有类属性上的[XmlElement(ElementName="xxx")]特性,让序列化器使用默认行为生成正确的列表结构,再通过正则表达式将节点名从PascalCase转为camelCase。
- 移除特性后的类定义:
[System.SerializableAttribute()] public class TestRequest { public List<PackageRoot>? Packages { get; set; } } [System.SerializableAttribute()] public partial class PackageRoot { public MyPackage? Package { get; set; } } [System.SerializableAttribute()] public partial class MyPackage { public string? Id { get; set; } }
- 修改序列化方法,添加正则转换:
private XmlDocument ConvertToXml(object o) { var ser = new XmlSerializer(o.GetType()); using (StringWriter writer = new StringWriter()) { ser.Serialize(writer, o); var xml = writer.ToString(); // 正则匹配XML节点名,将首字母转为小写实现驼峰 xml = Regex.Replace(xml, @"<(\w+)(?=>|\s)", match => "<" + char.ToLower(match.Groups[1].Value[0]) + match.Groups[1].Value.Substring(1) ); var doc = new XmlDocument(); doc.LoadXml(xml); return doc; } }
该正则会匹配所有XML节点的开头标签,将节点名的首字母转为小写,实现camelCase命名,同时保留了正确的列表结构。
内容的提问来源于stack exchange,提问作者kpg
相关产品推荐
相关产品推荐

