如何在XML序列化時隱藏可為null的的字段(XmlElement和XmlAttribute)


使用XmlSerializer可以方便的將對象序列化為xml,實現應用之間的數據交互。但是XmlSerializer卻不能很好地序列化類型中的可為null的字段。
例如,有如下定義的類Person:

C#代碼 
  1. [Serializable]  
  2. [XmlRoot(ElementName = "Person")]  
  3. public class Person  
  4. {  
  5.     public string FirstName { getset; }  
  6.   
  7.     public string LastName { getset; }  
  8.   
  9.     public int? Age { getset; }  
  10. }  
    [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類型。
我們的實例化代碼如下所示:

C#代碼 
  1. var person = new Person  
  2.                  {  
  3.                      FirstName = "First",  
  4.                  };  
  5. person.OutputXml(Console.Out);  
            var person = new Person
                             {
                                 FirstName = "First",
                             };
            person.OutputXml(Console.Out);


其中方法OutputXml為擴展方法,使用XmlSerializer來序列化對象,具體定義為:

C#代碼 
  1. public static void OutputXml<T>(this T dto, TextWriter textWriter)  
  2. {  
  3.     var xmlTypeMapping = typeof(T);  
  4.     var serializer = new XmlSerializer(xmlTypeMapping);  
  5.     var xmlns = new XmlSerializerNamespaces();  
  6.     xmlns.Add(string.Empty, string.Empty);  
  7.     using (var writer = new XmlTextWriter(textWriter) { Formatting = Formatting.Indented })  
  8.     {  
  9.         serializer.Serialize(writer, dto, xmlns);  
  10.     }  
  11. }  
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屬性:

C#代碼 
  1. [Serializable]  
  2. [XmlRoot(ElementName = "Person")]  
  3. public class Person  
  4. {  
  5.     [XmlAttribute]  
  6.     public string FirstName { getset; }  
  7.   
  8.     [XmlAttribute]  
  9.     public string LastName { getset; }  
  10.   
  11.     [XmlAttribute]  
  12.     public int? Age { getset; }  
  13. }  
    [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,並在其中實現定義我們的序列化規則:

C#代碼 
  1. [Serializable]  
  2. [XmlRoot(ElementName = "Person")]  
  3. public class Person  
  4. {  
  5.     public string FirstName { getset; }  
  6.   
  7.     public string LastName { getset; }  
  8.   
  9.     public int? Age { getset; }  
  10.   
  11.     public bool ShouldSerializeAge()  
  12.     {  
  13.         return Age.HasValue;  
  14.     }  
  15. }  
    [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類:

C#代碼 
  1. [Serializable]  
  2. [XmlRoot(ElementName = "Person")]  
  3. public class Person  
  4. {  
  5.     [XmlAttribute]  
  6.     public string FirstName { getset; }  
  7.   
  8.     [XmlAttribute]  
  9.     public string LastName { getset; }  
  10.   
  11.     [XmlIgnore]   // (4)   
  12.     public int? Age { getset; }  
  13.   
  14.     [XmlAttribute(AttributeName = "Age")]  // (1)  
  15.     public string AgeValue  
  16.     {  
  17.         get  
  18.         {  
  19.             // (2)  
  20.             return Age.HasValue ? Age.Value.ToString() : null;  
  21.         }  
  22.         set  
  23.         {  
  24.             int result;  
  25.             // (3)  
  26.             Age = int.TryParse(value, out result) ? result : (int?) null;  
  27.         }  
  28.     }  
  29. }  
    [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;
            }
        }
    }


注意類中注釋的部分:

  1. 為原可空屬性定義一個“虛擬”的屬性,該屬性可為任意名稱任意類型(示例里定義的為string類型的AgeValue),但注意要將該屬性的序 列化名稱置為原可空屬性Age。
  2. 在get方法里根據Age是否為空將其轉化為AgeValue。
  3. 在set方法里將輸入的value轉化為合適的值賦給可空屬性Age。
  4. 將原Age屬性標記為XmlIgnore,使XmlSerializer在序列化時忽略序列化該屬性。


使用上述方法,我們可以將Age序列化為XmlAttribute了,雖然實際上我們是“騙”了XmlSerializer並使用另一個屬性作為 Age屬性的值。
因此,當person對象中的Age為空時,我們得到如下的xml結果:

而當person對象中的Age不為空時,我們也可以正常的用XmlAttribute來表示Age的值了。


免責聲明!

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



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