在C#中序列化为XML时,将属性更改为camelCase

时间:2017-06-14 11:17:05

标签: c# xml camelcasing

我有多个(从)XML序列化的DTO类。我想使用PascalCase的C#约定来获取属性,但我希望它们在XML中显示为camelCase。

示例:

[XmlElement("config")]
public ConfigType Config { get; set; }

此处属性为Config,但在XML中显示为config

我觉得对每个属性使用[XmlAttribute]是浪费的,因为它们总是与属性名称相同,只有第一个字母没有大写。此外,如果我以后更改属性名称,我必须记住更改[XmlAttribute],否则它们将会不同步。

是否有可能有一个类范围的属性,即“即使它们是pascal case,也可以使用驼峰式的属性”,或者更好的是,设置为XmlSerializer?

2 个答案:

答案 0 :(得分:3)

您可以创建一个自定义XmlWriter,用以下内容包装由框架提供的XmlWriter:

public class MyXmlWriter : XmlWriter
{
    private bool disposedValue;
    private XmlWriter writer; // The XmlWriter that will actually write the xml
    public override WriteState WriteState => writer.WriteState;

    public MyXmlWriter(XmlWriter writer)
    {
        this.writer = writer;
    }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        localName = char.ToLower(localName[0]) + localName.Substring(1); // Assuming that your properties are in PascalCase we just need to lower-case the first letter.
        writer.WriteStartElement(prefix, localName, ns);
    }

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        // If you want to do the same with attributes you can do the same here
        writer.WriteStartAttribute(prefix, localName, ns); 
    }

    protected override void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                writer.Dispose();
                base.Dispose(disposing);
            }
            disposedValue = true;
        }
    }

    // Wrapping every other methods...
    public override void Flush()
    {
        writer.Flush();
    }

    public override string LookupPrefix(string ns)
    {
        return writer.LookupPrefix(ns);
    }

    public override void WriteBase64(byte[] buffer, int index, int count)
    {
        writer.WriteBase64(buffer, index, count);
    }

    public override void WriteCData(string text)
    {
        writer.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        writer.WriteCharEntity(ch);
    }

    public override void WriteChars(char[] buffer, int index, int count)
    {
        writer.WriteChars(buffer, index, count);
    }

    public override void WriteComment(string text)
    {
        writer.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset)
    {
        writer.WriteDocType(name, pubid, sysid, subset);
    }

    public override void WriteEndAttribute()
    {
        writer.WriteEndAttribute();
    }

    public override void WriteEndDocument()
    {
        writer.WriteEndDocument();
    }

    public override void WriteEndElement()
    {
        writer.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        writer.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        writer.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        writer.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(char[] buffer, int index, int count)
    {
        writer.WriteRaw(buffer, index, count);
    }

    public override void WriteRaw(string data)
    {
        writer.WriteRaw(data);
    }

    public override void WriteStartDocument()
    {
        writer.WriteStartDocument();
    }

    public override void WriteStartDocument(bool standalone)
    {
        writer.WriteStartDocument(standalone);
    }

    public override void WriteString(string text)
    {
        writer.WriteString(text);
    }

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        writer.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteWhitespace(string ws)
    {
        writer.WriteWhitespace(ws);
    }
}

然后像这样使用它:

public class CustomClassOne
{
    public string MyCustomName { get; set; }
    public CustomClassTwo MyOtherProperty { get; set; }
    public CustomClassTwo[] MyArray { get; set; }
}
public class CustomClassTwo
{
    public string MyOtherCustomName { get; set; }
}
.
.
.
static void Main(string[] args)
{
    var myObj = new CustomClassOne()
    {
        MyCustomName = "MYNAME",
        MyOtherProperty = new CustomClassTwo()
        {
            MyOtherCustomName = "MyOtherName"
        },
        MyArray = new CustomClassTwo[]
        {
            new CustomClassTwo(){MyOtherCustomName = "Elem1"},
            new CustomClassTwo(){MyOtherCustomName = "Elem2"}
        }
    };
    var sb = new StringBuilder();
    var serializer = new XmlSerializer(typeof(CustomClassOne));
    var settings = new XmlWriterSettings()
    {
        Indent = true // Indent it so we can see it better
    };
    using (var sw = new StringWriter(sb))
    using (var xw = new MyXmlWriter(XmlWriter.Create(sw, settings)))
    {
        serializer.Serialize(xw, myObj);
    }
    Console.WriteLine(sb.ToString());
}

输出将是:

<?xml version="1.0" encoding="utf-16"?>
<customClassOne xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <myCustomName>MYNAME</myCustomName>
  <myOtherProperty>
    <myOtherCustomName>MyOtherName</myOtherCustomName>
  </myOtherProperty>
  <myArray>
    <customClassTwo>
      <myOtherCustomName>Elem1</myOtherCustomName>
    </customClassTwo>
    <customClassTwo>
      <myOtherCustomName>Elem2</myOtherCustomName>
    </customClassTwo>
  </myArray>
</customClassOne>

为了能够反序列化,我们可以在XmlReader上创建一个包装器,就像在XmlWriter上创建的包装器一样:

public class MyXmlReader : XmlReader
{
    private bool disposedValue;
    private XmlReader reader;
    // The property names will be added in the XmlNameTable, so we wrap it with a simple class that lower-cases the first letter like we did previously
    private XmlNameTableWrapper nameTable;

    private class XmlNameTableWrapper : XmlNameTable
    {
        private XmlNameTable wrapped;
        // Some names that are added by default to this collection. We can skip the lower casing logic on them.
        private string[] defaultNames = new string[]
            {
                "http://www.w3.org/2001/XMLSchema","http://www.w3.org/2000/10/XMLSchema","http://www.w3.org/1999/XMLSchema","http://microsoft.com/wsdl/types/","http://www.w3.org/2001/XMLSchema-instance","http://www.w3.org/2000/10/XMLSchema-instance","http://www.w3.org/1999/XMLSchema-instance","http://schemas.xmlsoap.org/soap/encoding/","http://www.w3.org/2003/05/soap-encoding","schema","http://schemas.xmlsoap.org/wsdl/","arrayType","null","nil","type","arrayType","itemType","arraySize","Array","anyType"
            };

        public XmlNameTableWrapper(XmlNameTable wrapped)
        {
            this.wrapped = wrapped;
        }

        public override string Add(char[] array, int offset, int length)
        {
            if (array != null && array.Length > 0 && !defaultNames.Any(n => n == new string(array)))
            {
                array[0] = char.ToLower(array[0]);
            }
            return wrapped.Add(array, offset, length);
        }

        public override string Add(string array)
        {
            if (array != null && !defaultNames.Any(n => n == array))
            {
                if (array.Length < 2)
                {
                    array = array.ToLower();
                }
                else
                    array = char.ToLower(array[0]) + array.Substring(1);
            }
            return wrapped.Add(array);
        }

        public override string Get(char[] array, int offset, int length)
        {
            if (array != null && array.Length > 0 && !defaultNames.Any(n => n == new string(array)))
            {
                array[0] = char.ToLower(array[0]);
            }
            return wrapped.Get(array, offset, length);
        }

        public override string Get(string array)
        {
            if (array != null && !defaultNames.Any(n => n == array))
            {
                if (array.Length < 2)
                {
                    array = array.ToLower();
                }
                array = char.ToLower(array[0]) + array.Substring(1);
            }
            return wrapped.Get(array);
        }
    }

    public MyXmlReader(XmlReader reader)
    {
        this.reader = reader;
        nameTable = new XmlNameTableWrapper(reader.NameTable);
    }

    protected override void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                reader.Dispose();
                base.Dispose(disposing);
            }
            disposedValue = true;
        }
    }

    // Instead of returning reader.NameTable we return the wrapper that will care to populate it
    public override XmlNameTable NameTable => nameTable;

    // Everything else does not need additional logic...
    public override XmlNodeType NodeType => reader.NodeType;

    public override string LocalName => reader.LocalName;

    public override string NamespaceURI => reader.NamespaceURI;

    public override string Prefix => reader.Prefix;

    public override string Value => reader.Value;

    public override int Depth => reader.Depth;

    public override string BaseURI => reader.BaseURI;

    public override bool IsEmptyElement => reader.IsEmptyElement;

    public override int AttributeCount => reader.AttributeCount;

    public override bool EOF => reader.EOF;

    public override ReadState ReadState => reader.ReadState;

    public override string GetAttribute(string name)
    {
        return reader.GetAttribute(name);
    }

    public override string GetAttribute(string name, string namespaceURI)
    {
        return reader.GetAttribute(name, namespaceURI);
    }

    public override string GetAttribute(int i)
    {
        return reader.GetAttribute(i);
    }

    public override string LookupNamespace(string prefix)
    {
        return reader.LookupNamespace(prefix);
    }

    public override bool MoveToAttribute(string name)
    {
        return reader.MoveToAttribute(name);
    }

    public override bool MoveToAttribute(string name, string ns)
    {
        return reader.MoveToAttribute(name, ns);
    }

    public override bool MoveToElement()
    {
        return reader.MoveToElement();
    }

    public override bool MoveToFirstAttribute()
    {
        return reader.MoveToFirstAttribute();
    }

    public override bool MoveToNextAttribute()
    {
        return reader.MoveToNextAttribute();
    }

    public override bool Read()
    {
        return reader.Read();
    }

    public override bool ReadAttributeValue()
    {
        return reader.ReadAttributeValue();
    }

    public override void ResolveEntity()
    {
        reader.ResolveEntity();
    }
}

然后像这样使用它:

var serializer = new XmlSerializer(typeof(CustomClassOne));
using (var sr = new StringReader(theXmlGeneratedBefore))
using (var xr = new MyXmlReader(XmlReader.Create(sr)))
{
    var o = serializer.Deserialize(xr);
}

答案 1 :(得分:2)

这很可能不是最好的解决方案(从性能的角度来看肯定是最好的),但我认为它非常简洁明了。

使用PascalCasing采样数据对象

public class ReportSummary
{
    public string Name { get; set; }
    public bool IsSuccess { get; set; }
}

利用Newtonsoft.Json的优势

var summary = new ReportSummary { Name = "My Awesome Report", IsSuccess = true };

var resolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() };
var settings = new JsonSerializerSettings { ContractResolver = resolver };

var json = JsonConvert.SerializeObject(summary, settings);
XNode xml = JsonConvert.DeserializeXNode(json, ToCamelCasing(nameof(ReportSummary)));
Console.WriteLine(xml.ToString());

顶级节点的ToCamelCasing助手

static string ToCamelCasing(string input) 
  => char.ToLowerInvariant(input[0]) + input.Substring(1);

带有camelCasing的输出

<reportSummary>
  <name>My Awesome Report</name>
  <isSuccess>true</isSuccess>
</reportSummary>