簡體   English   中英

在 C# 中序列化為 XML 時將屬性更改為駝峰式大小寫

[英]Change properties to camelCase when serializing to XML in C#

我有多個 DTO 類,它們從 XML 序列化(反)序列化到 XML。 我想對屬性使用 PascalCase 的 C# 約定,但我希望它們在 XML 中顯示為駝峰式大小寫。

示例:

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

這里的屬性是Config但在 XML 中顯示為config

我覺得為每個屬性使用一個[XmlAttribute]很浪費,因為它們總是與屬性名稱相同,只有第一個字母不大寫。 此外,如果我將來更改屬性名稱,我必須記住更改[XmlAttribute]否則它們將變得不同步。

是否有可能有一個類范圍的屬性,表示“即使屬性是 pascal 大小寫,也使用駝峰大小寫”,或者更好的是 XmlSerializer 的設置?

您可以創建一個自定義 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);
}

這很可能不是最好的解決方案(從性能角度肯定),但在我看來它非常簡潔和整潔。

使用 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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM