C# XML序列化/反序列化參考


.NET提供了很不錯的XML序列化/反序列化器,(它們所在的命名空間為System.Xml.Serialization)這是很方便的,下面對它的使用做一些總結,以供參考。

1,簡單序列化

public static string SerializeXml(object data) {
    using (StringWriter sw = new StringWriter()) {
        XmlSerializer xz = new XmlSerializer(data.GetType());
        xz.Serialize(sw, data);
        return sw.ToString();
    }
}

以上代碼是序列化為字符串,如果需要以流的形式返回序列化結果給客戶端,或寫入文件,那么通常需要選擇一種編碼,常見的編碼格式是UTF-8,但某些特殊場合也許你會被要求使用GB2312編碼,下面例子是使用GB2312編碼的情況:

public static MemoryStream SerializeXml(object data) {
    MemoryStream ms = new MemoryStream();
    StreamWriter sw = new StreamWriter(ms, Encoding.GetEncoding("GB2312"));
    XmlSerializer xz = new XmlSerializer(data.GetType());
    xz.Serialize(sw, data);
    return ms;
}

這樣就直接把對象以特定編碼格式序列化到MemoryStream里去了,當然也許你想到了,先使用前面的SerializeXml生成字符串,再把字符串以特定編碼格式寫到流或者字節數組中去不行嗎?當然行,不過這樣會多出一步,不夠直接。

這里還有個要注意的地方,序列化到流的時候,不要對Stream及TextWriter對象包在using里,因為這樣會導致流返回的時候已經被關閉。

2,簡單反序列化

FileStream fs = File.Open("file.xml", FileMode.Open);
using (StreamReader sr = new StreamReader(fs, Encoding.UTF8)) {
    XmlSerializer xz = new XmlSerializer(typeof(Department));
    Department dept = (Department)xz.Deserialize(sr);
    //blah blah ...
}

其中Department是你要反序列化出來的類,同樣需要注意編碼,這里指定的是UTF-8,但不排除有別的可能。

其實序列化和反序列化時可逆的,你通過怎樣的類和編碼把對象序列化成xml,就能通過怎樣的類和編碼將xml反序列化成對象。

3,指定XML標簽的名字

[XmlRoot("department")]
public class Department {
    public string DeptName { get; set; }

    [XmlElement("extra")]
    public DeptExtraInfo DeptExtraInfo { get; set; }
}

通過XmlRoot注解和XmlElement注解即可實現,其中XmlRoot用於指定“根”,也就是XML的最上一層的Tag。

4,指定XML標簽的屬性

[XmlRoot("department")]
public class Department {
    public string DeptName { get; set; } = "研發部";

    [XmlAttribute("timestamp")]
    public int Timestamp = 10;
}

利用XmlAttribute注解,這么一來,Timestamp就成為了department這個根節點的timestamp屬性。

5,去掉不需要的namespace

默認情況下,xml的頭會帶上類似這樣的一個namespace:

<department xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- blah blah blah -->
</department>

你不需要的話可以修改一下序列化方法:

public static string SerializeXml(object data) {
    using (Utf8Writer sw = new Utf8Writer()) {
        XmlSerializer xz = new XmlSerializer(data.GetType());
        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
        ns.Add("", "");
        xz.Serialize(sw, data, ns);
        return sw.ToString();
    }
}

6,序列化集合的時候不要“再包一層”

這個怎么說呢?先看這么一個類:

    [XmlRoot("department")]
    public class Department {
        public string DeptName { get; set; };

        public List<Employee> Details { get; set; };
    }

序列化出來的結果是:

<?xml version="1.0" encoding="utf-8"?>
<department>
  <DeptName>研發部</DeptName>
  <Employees>
    <Employee>
      <EmpName>張三</EmpName>
      <EmpSalary>10000</EmpSalary>
    </Employee>
    <Employee>
      <EmpName>李四</EmpName>
      <EmpSalary>8000</EmpSalary>
    </Employee>
  </Employees>
</department>

注意Employee這個標簽外面包了一層Employees,這個也許不是你想要的結果,這才是你想要的結果:

<?xml version="1.0" encoding="utf-8"?>
<department>
  <DeptName>研發部</DeptName>
  <Employee>
    <EmpName>張三</EmpName>
    <EmpSalary>10000</EmpSalary>
  </Employee>
  <Employee>
    <EmpName>李四</EmpName>
    <EmpSalary>8000</EmpSalary>
  </Employee>
</department>

這個怎么做呢?很簡單,在Employees前面加個XmlElement注解即可:

    [XmlRoot("department")]
    public class Department {
        public string DeptName { get; set; } = "研發部";

        [XmlElement("Employee")]
        public List<Employee> Employees { get; set; } = new List<Employee>();
    }

另外,如果是只是想改一下之前的Employees標簽的名字的話,用這樣一個注解:[XmlArray("NewName")]。

7,序列化null值屬性

默認情況下,null值的屬性是不會被序列化的,想想看為什么?

因為生成<DeptName />這樣的序列化結果的話,沒辦法知道DeptName到底是null還是空字符串,所以比較好的解決方法是在序列化之前,把null字符串填充為空字符串。可以考慮寫一個幫助方法,利用反射遍歷一個對象里的所有字符串屬性,將null設置為空字符串,當然了,實際的情況要考慮得更全面點,比如對象里還有對象,而且還包含可枚舉對象的情況,估計得使用遞歸。篇幅問題,代碼我就不貼了。

另外還有一種比較地道的做法,不需要改變對象的值,那就是在對象上加上[XmlElement(IsNullable = true)]注解,但這樣帶來的問題就是會在序列化生成的tag中多出一個xsi:nil="true"這樣的屬性來。

8,手工反序列化

有些情況實在太特殊,沒辦法直接用簡單的Deserialize方法來反序列化,例如這個XML:

<?xml version="1.0" encoding="UTF-8"?>
<ns0:DeliveryAddressUpdate_S10 xmlns:ns0="urn:ABC:GAIA:CN:LoadSetNoAndChineseDelAddr:ISC0186">
  <Line>
    <ASNNNB>95175154 </ASNNNB>
    <CHDANR>00476</CHDANR>
    <ASCUID>SHD3SHD3</ASCUID>
    <IGAAUC>上海</IGAAUC>
    <IGAAUC>閔行區</IGAAUC>
    <IGAAUC>七莘路8888號</IGAAUC>
    <IGAAUC>XXXX大樓XXXX室</IGAAUC>
  </Line>
  <Line>
    <ASNNNB>124321 </ASNNNB>
    <CHDANR>4321</CHDANR>
    <ASCUID>4312</ASCUID>
    <IGAAUC>上海</IGAAUC>
    <IGAAUC>浦東新區</IGAAUC>
    <IGAAUC>浦東大道9999號</IGAAUC>
    <IGAAUC>YYYY大樓YYYY室</IGAAUC>
  </Line>
</ns0:DeliveryAddressUpdate_S10>

首先根節點很奇葩,默認反序列化器不認,另外就是IGAAUC,重復多次,它的意圖是說重復的這幾個IGAAUC拼接在一起,生成一個地址,這個默認的反序列化顯然做不到,手工讀吧,參考代碼如下:

List<Address> addrList = new List<Address>();
Address currentAddress = new Address();
XmlTextReader reader = new XmlTextReader(new MemoryStream(File.ReadAllBytes("test.xml")));
while (reader.Read()) {
    if (reader.IsStartElement()) {
        switch (reader.Name) {
            case "Line":
                currentAddress = new Address();
                addrList.Add(currentAddress);
                break;
            case "ASNNNB":
                currentAddress.Asnnb = reader.ReadString();
                break;
            case "CHDANR":
                currentAddress.Chdanr = reader.ReadString();
                break;
            case "ASCUID":
                currentAddress.Ascuid = reader.ReadString();
                break;
            case "IGAAUC":
                currentAddress.Igaauc += reader.ReadString().Trim() + "\r\n";
                break;
        }
    }
}
//addrList便是結果

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM