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

如何自动反序列化大小写不统一的XML

自定义XML序列化/反序列化实现自动命名规则转换

问题背景

现有遵循特殊命名规则的XML:

<DOC_ROOT>
    <LIST_WRAPPER
        wrapperAttribute = "Something"
    >
        <LIST_ELEMENT
                elementAttribute = "Non-Default" >Element Text</LIST_ELEMENT>
        <LIST_ELEMENT
                elementAttribute = "Something Else" >Different Element Text</LIST_ELEMENT>
    </LIST_WRAPPER>
</DOC_ROOT>

对应的C#记录类采用PascalCase命名:

[XmlRoot]
public record DocRoot
{
    [XmlElement]
    public ListWrapper ListWrapper { get; set; }
}

public record ListWrapper
{
    [XmlAttribute]
    public string WrapperAttribute { get; set; }
    [XmlElement]
    public List<ListElement> ListElement { get; set; }
}

public record ListElement
{
    [XmlAttribute]
    public string ElementAttribute { get; set; }
    [XmlText]
    public string Text { get; set; }
}

默认XmlSerializer无法自动映射XML的特殊命名(全大写标签、小驼峰属性)与C#的PascalCase命名,手动添加[XmlRoot]/[XmlAttribute]等特性过于繁琐,且希望避免XML转JSON的额外性能开销,需实现自定义序列化组件自动处理命名转换。

解决方案:自定义XmlReader与XmlWriter

通过封装XmlTextReader和XmlTextWriter,在读写XML时自动完成命名规则转换,无需修改现有实体类的特性标注。

1. 命名转换工具类

实现通用的命名转换方法,处理XML与C#命名风格的互转:

public static class NamingConverter
{
    // XML全大写标签(如LIST_WRAPPER)转PascalCase(ListWrapper)
    public static string XmlTagToPascalCase(string xmlTag)
    {
        if (string.IsNullOrEmpty(xmlTag)) return xmlTag;
        
        var parts = xmlTag.Split('_', StringSplitOptions.RemoveEmptyEntries);
        return string.Concat(parts.Select(p => char.ToUpper(p[0]) + p.Substring(1).ToLower()));
    }

    // XML小驼峰属性(如wrapperAttribute)转PascalCase(WrapperAttribute)
    public static string XmlAttributeToPascalCase(string xmlAttr)
    {
        if (string.IsNullOrEmpty(xmlAttr)) return xmlAttr;
        
        var chars = xmlAttr.ToCharArray();
        chars[0] = char.ToUpper(chars[0]);
        for (int i = 1; i < chars.Length; i++)
        {
            if (chars[i] == '_')
            {
                if (i + 1 < chars.Length)
                {
                    chars[i + 1] = char.ToUpper(chars[i + 1]);
                }
                Array.Copy(chars, i + 1, chars, i, chars.Length - i - 1);
                Array.Resize(ref chars, chars.Length - 1);
                i--;
            }
        }
        return new string(chars);
    }

    // PascalCase(ListWrapper)转XML全大写标签(LIST_WRAPPER)
    public static string PascalCaseToXmlTag(string pascalCase)
    {
        if (string.IsNullOrEmpty(pascalCase)) return pascalCase;
        
        var sb = new StringBuilder();
        foreach (var c in pascalCase)
        {
            if (char.IsUpper(c) && sb.Length > 0)
            {
                sb.Append('_');
            }
            sb.Append(char.ToUpper(c));
        }
        return sb.ToString();
    }

    // PascalCase(WrapperAttribute)转XML小驼峰属性(wrapperAttribute)
    public static string PascalCaseToXmlAttribute(string pascalCase)
    {
        if (string.IsNullOrEmpty(pascalCase)) return pascalCase;
        
        var sb = new StringBuilder();
        sb.Append(char.ToLower(pascalCase[0]));
        for (int i = 1; i < pascalCase.Length; i++)
        {
            if (char.IsUpper(pascalCase[i]))
            {
                sb.Append('_');
                sb.Append(char.ToLower(pascalCase[i]));
            }
            else
            {
                sb.Append(pascalCase[i]);
            }
        }
        return sb.ToString();
    }
}

2. 自定义XmlReader(反序列化用)

重写XmlTextReader的相关属性,在读取时自动将XML命名转换为PascalCase:

public class CustomXmlReader : XmlTextReader
{
    public CustomXmlReader(Stream stream) : base(stream) { }
    public CustomXmlReader(string url) : base(url) { }
    public CustomXmlReader(TextReader reader) : base(reader) { }

    public override string LocalName
    {
        get
        {
            // 区分标签和属性,应用对应转换规则
            if (NodeType == XmlNodeType.Attribute)
            {
                return NamingConverter.XmlAttributeToPascalCase(base.LocalName);
            }
            else if (NodeType == XmlNodeType.Element)
            {
                return NamingConverter.XmlTagToPascalCase(base.LocalName);
            }
            return base.LocalName;
        }
    }

    public override string Name => LocalName;
}

3. 自定义XmlWriter(序列化用)

重写写入元素和属性的方法,将PascalCase命名转换为XML所需的命名规则:

public class CustomXmlWriter : XmlTextWriter
{
    public CustomXmlWriter(Stream stream, Encoding encoding) : base(stream, encoding) { }
    public CustomXmlWriter(string filename, Encoding encoding) : base(filename, encoding) { }
    public CustomXmlWriter(TextWriter writer) : base(writer) { }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        var xmlTag = NamingConverter.PascalCaseToXmlTag(localName);
        base.WriteStartElement(prefix, xmlTag, ns);
    }

    public override void WriteAttributeString(string prefix, string localName, string ns, string value)
    {
        var xmlAttr = NamingConverter.PascalCaseToXmlAttribute(localName);
        base.WriteAttributeString(prefix, xmlAttr, ns, value);
    }
}

4. 使用示例

反序列化XML到C#对象

using var reader = new StringReader(xmlContent); // xmlContent为目标XML字符串
using var customReader = new CustomXmlReader(reader);
var serializer = new XmlSerializer(typeof(DocRoot));
var docRoot = (DocRoot)serializer.Deserialize(customReader);

序列化C#对象到XML

var docRoot = new DocRoot
{
    ListWrapper = new ListWrapper
    {
        WrapperAttribute = "Something",
        ListElement = new List<ListElement>
        {
            new ListElement { ElementAttribute = "Non-Default", Text = "Element Text" },
            new ListElement { ElementAttribute = "Something Else", Text = "Different Element Text" }
        }
    }
};

using var writer = new StringWriter();
using var customWriter = new CustomXmlWriter(writer);
customWriter.Formatting = Formatting.Indented;
var serializer = new XmlSerializer(typeof(DocRoot));
serializer.Serialize(customWriter, docRoot);
var xmlContent = writer.ToString();

注意事项

  • 若XML命名规则有变化,只需修改NamingConverter中的转换逻辑即可,无需改动实体类或序列化/反序列化的调用代码。
  • 自定义Reader/Writer仅处理命名转换,其他XML读写逻辑完全继承自XmlTextReader/XmlTextWriter,兼容性与原生组件一致。

内容的提问来源于stack exchange,提问作者Christopher Walters

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 13:07:52