如何自动反序列化大小写不统一的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
相关产品推荐
相关产品推荐

