使用XmlSerializer可以方便的將對象序列化為xml,實現應用之間的數據交互。但是XmlSerializer卻不能很好地序列化類型中的可為null的字段。
例如,有如下定義的類Person:
- [Serializable]
- [XmlRoot(ElementName = "Person")]
- public class Person
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public int? Age { get; set; }
- }
[Serializable] [XmlRoot(ElementName = "Person")] public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int? Age { get; set; } }
其中的Age屬性為Nullable int類型。
我們的實例化代碼如下所示:
- var person = new Person
- {
- FirstName = "First",
- };
- person.OutputXml(Console.Out);
var person = new Person { FirstName = "First", }; person.OutputXml(Console.Out);
其中方法OutputXml為擴展方法,使用XmlSerializer來序列化對象,具體定義為:
- public static void OutputXml<T>(this T dto, TextWriter textWriter)
- {
- var xmlTypeMapping = typeof(T);
- var serializer = new XmlSerializer(xmlTypeMapping);
- var xmlns = new XmlSerializerNamespaces();
- xmlns.Add(string.Empty, string.Empty);
- using (var writer = new XmlTextWriter(textWriter) { Formatting = Formatting.Indented })
- {
- serializer.Serialize(writer, dto, xmlns);
- }
- }
public static void OutputXml<T>(this T dto, TextWriter textWriter) { var xmlTypeMapping = typeof(T); var serializer = new XmlSerializer(xmlTypeMapping); var xmlns = new XmlSerializerNamespaces(); xmlns.Add(string.Empty, string.Empty); using (var writer = new XmlTextWriter(textWriter) { Formatting = Formatting.Indented }) { serializer.Serialize(writer, dto, xmlns); } }
使用上述方法序列化對象person,得到的結果為:
注意到雖然Age屬性為空,卻仍然被序列化成 了古怪的xml,這往往不是所期望的結果。事實上同為空的LastName屬性的序列化正是大多數情況下我們所期望的行為——忽略為空的屬性。
另一方面,如果試圖序列化類屬性為xml 屬性(而非xml元素),則甚至不能工作。例如如果我們將Person類的定義修改成如下形式以便序列化為xml屬性:
- [Serializable]
- [XmlRoot(ElementName = "Person")]
- public class Person
- {
- [XmlAttribute]
- public string FirstName { get; set; }
- [XmlAttribute]
- public string LastName { get; set; }
- [XmlAttribute]
- public int? Age { get; set; }
- }
[Serializable] [XmlRoot(ElementName = "Person")] public class Person { [XmlAttribute] public string FirstName { get; set; } [XmlAttribute] public string LastName { get; set; } [XmlAttribute] public int? Age { get; set; } }
Xmlserializer甚至無法正常序列化上面同樣的person對象並拋出如下“XmlAttribute/XmlText cannot be used to encode complex types”的錯誤:
如何解決上述的2個問題呢?
1. 序列化可為null屬性輸出為XmlElement
為了在序列化可空屬性的時候忽略空值的古怪輸出,我們可以在Person類中定義一個返回bool的特殊方法 ShouldSerializeAge,並在其中實現定義我們的序列化規則:
- [Serializable]
- [XmlRoot(ElementName = "Person")]
- public class Person
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public int? Age { get; set; }
- public bool ShouldSerializeAge()
- {
- return Age.HasValue;
- }
- }
[Serializable] [XmlRoot(ElementName = "Person")] public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int? Age { get; set; } public bool ShouldSerializeAge() { return Age.HasValue; } }
在這里我們定義序列化規則為:序列化非空Age。注意該方法的名字一定是以ShouldSerialize開頭並連接上需要自定義規則的屬性名 稱。這樣序列化出來的person對象為:
空的Age和空的LastName一樣在序列化時被忽略了輸出。正是我們期望的結果!
2. 序列化可為null的屬性輸出為XmlAttribute
由於可空屬性無法被直接序列化為XmlAttribute,我們需要采用間接的辦法——定義間接屬性。此時我們可以如下定義Person類:
- [Serializable]
- [XmlRoot(ElementName = "Person")]
- public class Person
- {
- [XmlAttribute]
- public string FirstName { get; set; }
- [XmlAttribute]
- public string LastName { get; set; }
- [XmlIgnore] // (4)
- public int? Age { get; set; }
- [XmlAttribute(AttributeName = "Age")] // (1)
- public string AgeValue
- {
- get
- {
- // (2)
- return Age.HasValue ? Age.Value.ToString() : null;
- }
- set
- {
- int result;
- // (3)
- Age = int.TryParse(value, out result) ? result : (int?) null;
- }
- }
- }
[Serializable] [XmlRoot(ElementName = "Person")] public class Person { [XmlAttribute] public string FirstName { get; set; } [XmlAttribute] public string LastName { get; set; } [XmlIgnore] // (4) public int? Age { get; set; } [XmlAttribute(AttributeName = "Age")] // (1) public string AgeValue { get { // (2) return Age.HasValue ? Age.Value.ToString() : null; } set { int result; // (3) Age = int.TryParse(value, out result) ? result : (int?) null; } } }
注意類中注釋的部分:
- 為原可空屬性定義一個“虛擬”的屬性,該屬性可為任意名稱任意類型(示例里定義的為string類型的AgeValue),但注意要將該屬性的序 列化名稱置為原可空屬性Age。
- 在get方法里根據Age是否為空將其轉化為AgeValue。
- 在set方法里將輸入的value轉化為合適的值賦給可空屬性Age。
- 將原Age屬性標記為XmlIgnore,使XmlSerializer在序列化時忽略序列化該屬性。
使用上述方法,我們可以將Age序列化為XmlAttribute了,雖然實際上我們是“騙”了XmlSerializer並使用另一個屬性作為 Age屬性的值。
因此,當person對象中的Age為空時,我們得到如下的xml結果:
而當person對象中的Age不為空時,我們也可以正常的用XmlAttribute來表示Age的值了。