C#的XML序列化/反序列化的一些常用操作。
.NET提供了很不錯的XML序列化/反序列化器,(它們所在的命名空間為System.Xml.Serialization)這是很友善的,下面對它的使用做一些總結,以供參考。
以上代碼是序列化為字元串,如果需要以流的形式傳回序列化結果給用戶端,或寫入檔案,那麼通常需要選擇一種編碼,常見的編碼格式是UTF-8,但某些特殊場合也許你會被要求使用GB2312編碼,下面例子是使用GB2312編碼的情況:
這樣就直接把對象以特定編碼格式序列化到MemoryStream裡去了,當然也許你想到了,先使用前面的SerializeXml生成字元串,再把字元串以特定編碼格式寫到流或者位元組數組中去不行嗎?當然行,不過這樣會多出一步,不夠直接。
這裡還有個要注意的地方,序列化到流的時候,不要對Stream及TextWriter對象包在using裡,因為這樣會導緻流傳回的時候已經被關閉。
其中Department是你要反序列化出來的類,同樣需要注意編碼,這裡指定的是UTF-8,但不排除有别的可能。
其實序列化和反序列化時可逆的,你通過怎樣的類和編碼把對象序列化成xml,就能通過怎樣的類和編碼将xml反序列化成對象。
通過XmlRoot注解和XmlElement注解即可實作,其中XmlRoot用于指定“根”,也就是XML的最上一層的Tag。
利用XmlAttribute注解,這麼一來,Timestamp就成為了department這個根節點的timestamp屬性。
預設情況下,xml的頭會帶上類似這樣的一個namespace:
你不需要的話可以修改一下序列化方法:
上面提到的Utf8Writer是指定要用UTF-8的編碼輸出,包括xml頭部的encoding屬性,的代碼如下:
順便提一下,一般情況下,我們都會使用UTF-8編碼,但也有特殊情況(在對接一些曆史遺留系統時)下是需要用GBK (現在和GB2312算是同義) 的,這種情況下,Encoding改為:
這個怎麼說呢?先看這麼一個類:
序列化出來的結果是:
注意Employee這個标簽外面包了一層Employees,這個也許不是你想要的結果,這才是你想要的結果:
這個怎麼做呢?很簡單,在Employees前面加個XmlElement注解即可:
另外,如果是隻是想改一下之前的Employees标簽的名字的話,用這樣一個注解:[XmlArray("NewName")]。
預設情況下,null值的屬性是不會被序列化的,想想看為什麼?
因為生成<DeptName />這樣的序列化結果的話,沒辦法知道DeptName到底是null還是空字元串,是以比較好的解決方法是在序列化之前,把null字元串填充為空字元串。可以考慮寫一個幫助方法,利用反射周遊一個對象裡的所有字元串屬性,将null設定為空字元串,當然了,實際的情況要考慮得更全面點,比如對象裡還有對象,而且還包含可枚舉對象的情況,估計得使用遞歸。篇幅問題,代碼我就不貼了。
另外還有一種比較道地的做法,不需要改變對象的值,那就是在對象上加上[XmlElement(IsNullable = true)]注解,但這樣帶來的問題就是會在序列化生成的tag中多出一個xsi:nil="true"這樣的屬性來。
有些情況實在太特殊,沒辦法直接用簡單的Deserialize方法來反序列化,例如這個XML:
首先根節點很奇葩,預設反序列化器不認,另外就是IGAAUC,重複多次,它的意圖是說重複的這幾個IGAAUC拼接在一起,生成一個位址,這個預設的反序列化顯然做不到,手工讀吧,參考代碼如下:
預設情況下,如果XML的标簽中的内容為空的話,标簽就“自閉合”(self-closing),如:
而我們想要這樣的效果:
那就需要自己對XmlWriter進行一些修改,用一個新的序列化的方法:
上面的代碼中還包括了XmlWriterSettings,它可以指定縮進,換行等格式。對于XmlWriter,用XmlWriterForceFullEnd進行一下包裝,XmlWriterForceFullEnd的代碼如下:
其實關鍵的就是WriteEndElement這個方法,一行代碼而已,但由于XmlWriter 是抽象類,很多虛方法得這麼轉一下。