You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

  1. 移除特性后的类定义:
[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; }
}
  1. 修改序列化方法,添加正则转换:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 09:34:59