2009-08-18 85 views
60

我從第三方獲得一個xml,我需要將它反序列化爲C#對象。這個XML可能包含整型或空值的屬性:attr =「11」或attr =「」。我想將此屬性值反序列化到具有可爲空的整數類型的屬性中。但XmlSerializer不支持反序列化爲可空類型。以下測試代碼在創建XmlSerializer時出現InvalidOperationException異常{「存在反映類型'TestConsoleApplication.SerializeMe'的錯誤。」}。使用XmlSerializer將空的xml屬性值反序列化爲可空屬性的int屬性

[XmlRoot("root")] 
public class SerializeMe 
{ 
    [XmlElement("element")] 
    public Element Element { get; set; } 
} 

public class Element 
{ 
    [XmlAttribute("attr")] 
    public int? Value { get; set; } 
} 

class Program { 
    static void Main(string[] args) { 
     string xml = "<root><element attr=''>valE</element></root>"; 
     var deserializer = new XmlSerializer(typeof(SerializeMe)); 
     Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml)); 
     var result = (SerializeMe)deserializer.Deserialize(xmlStream); 
    } 
} 

當我改變 '值' 屬性的類型爲int,反序列化失敗,出現InvalidOperationException:

有XML文檔(1,16)中的錯誤。

任何人都可以建議如何將空值的屬性反序列化爲可爲空的類型(作爲空),同時將非空屬性值反序列化爲整數?這有沒有什麼竅門,所以我不需要手動對每個域進行反序列化(實際上有很多)。從ahsteele評論後

更新:

  1. Xsi:nil attribute

    據我所知,這個屬性僅適用於XmlElementAttribute - 這個屬性指定元素沒有內容,無論是子元素或身體文本。但我需要找到XmlAttributeAttribute的解決方案。無論如何,我不能改變XML,因爲我無法控制它。

  2. bool *Specified property

    此屬性只能當屬性值不爲空時或屬性缺失。當attr具有空值(attr =')時,XmlSerializer構造函數失敗(如預期的那樣)。

    public class Element 
    { 
        [XmlAttribute("attr")] 
        public int Value { get; set; } 
    
        [XmlIgnore] 
        public bool ValueSpecified; 
    } 
    
  3. Custom Nullable class like in this blog post by Alex Scordellis

    我試圖從這個博客帖子我的問題採取類:

    [XmlAttribute("attr")] 
    public NullableInt Value { get; set; } 
    

    但XmlSerializer的構造失敗,出現InvalidOperationException:

    無法序列成員類型TestConsoleApplication.NullableInt的'Value'。

    XmlAttribute/XMLTEXT不能用於編碼實現IXmlSerializable的類型}

  4. 醜陋的替代品溶液(可恥的是我,我在這裏寫了這個代碼:)):

    public class Element 
    { 
        [XmlAttribute("attr")] 
        public string SetValue { get; set; } 
    
        public int? GetValue() 
        { 
         if (string.IsNullOrEmpty(SetValue) || SetValue.Trim().Length <= 0) 
          return null; 
    
         int result; 
         if (int.TryParse(SetValue, out result)) 
          return result; 
    
         return null; 
        } 
    } 
    

    但我不我不想拿出這樣的解決方案,因爲它打破了我的班級爲其消費者的界面。我會更好地手動實現IXmlSerializable接口。

目前,它看起來像我要實現IXmlSerializable的整個元素類(這是很大的),並沒有簡單的解決方法......

回答

17

我通過實現IXmlSerializable接口解決了這個問題。我沒有找到更簡單的方法。

下面是測試代碼示例:

[XmlRoot("root")] 
public class DeserializeMe { 
    [XmlArray("elements"), XmlArrayItem("element")] 
    public List<Element> Element { get; set; } 
} 

public class Element : IXmlSerializable { 
    public int? Value1 { get; private set; } 
    public float? Value2 { get; private set; } 

    public void ReadXml(XmlReader reader) { 
     string attr1 = reader.GetAttribute("attr"); 
     string attr2 = reader.GetAttribute("attr2"); 
     reader.Read(); 

     Value1 = ConvertToNullable<int>(attr1); 
     Value2 = ConvertToNullable<float>(attr2); 
    } 

    private static T? ConvertToNullable<T>(string inputValue) where T : struct { 
     if (string.IsNullOrEmpty(inputValue) || inputValue.Trim().Length == 0) { 
      return null; 
     } 

     try { 
      TypeConverter conv = TypeDescriptor.GetConverter(typeof(T)); 
      return (T)conv.ConvertFrom(inputValue); 
     } 
     catch (NotSupportedException) { 
      // The conversion cannot be performed 
      return null; 
     } 
    } 

    public XmlSchema GetSchema() { return null; } 
    public void WriteXml(XmlWriter writer) { throw new NotImplementedException(); } 
} 

class TestProgram { 
    public static void Main(string[] args) { 
     string xml = @"<root><elements><element attr='11' attr2='11.3'/><element attr='' attr2=''/></elements></root>"; 
     XmlSerializer deserializer = new XmlSerializer(typeof(DeserializeMe)); 
     Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml)); 
     var result = (DeserializeMe)deserializer.Deserialize(xmlStream); 
    } 
} 
9

我已經系列化亂搞的晚了很多自己並發現以下文章和帖子在處理值類型的空數據時很有用。

How to make a value type nullable with XmlSerializer in C# - serialization的答案詳細介紹了XmlSerializer的一個非常漂亮的技巧。具體來說,XmlSerialier會查找一個XXXSpecified布爾屬性來確定是否應該包含允許忽略空值的值。

Alex Scordellis問一個StackOverflow問題,它收到a good answer。亞歷克斯在他的博客上也做了一個關於他試圖解決的問題的好記錄Using XmlSerializer to deserialize into a Nullable<int>

有關Xsi:nil Attribute Binding Support的MSDN文檔也很有用。正如關於IXmlSerializable Interface的文檔一樣,儘管編寫自己的實現應該是最後的手段。

+0

「使用XmlSerializer的反序列化到一個可爲空值」的鏈接是死。 [這是Google的緩存版本](http://webcache.googleusercontent.com/search?q=cache:vT5GiyOCWyIJ:www.rqna.net/qna/zzrzt-deserialise-missing-xml-attribute-to-nullable-type .html) – Anttu 2014-06-13 08:09:48

+0

@Anttu我將原始*的Wayback Machine存檔的答案切換爲鏈接,使用XmlSerializer將其反序列化爲Nullable *。 – ahsteele 2014-06-16 21:34:46

43

這應該工作:

[XmlIgnore] 
public int? Age { get; set; } 

[XmlElement("Age")] 
public string AgeAsText 
{ 
    get { return (Age.HasValue) ? Age.ToString() : null; } 
    set { Age = !string.IsNullOrEmpty(value) ? int.Parse(value) : default(int?); } 
} 
+4

這會起作用,但這是與我的問題相同的解決方案。我不想將代理字段引入我的班級的公共界面。謝謝 – 2009-09-27 00:10:00

+4

FWIW,我發現這個解決方案比顯式的IXmlSerializable實現(公認的解決方案)更好,但不是OP的具體問題。除非絕對需要,否則我會避免實現IXmlSerializable,發現它在長期維護中花費更多。在這樣簡單的情況下,如果沒有其他任何緩解因素,我會選擇「醜陋」的替代解決方案,而不必再考慮其他問題。 – 2014-12-24 16:53:27

2

思想我還不如把我的回答到帽子: 通過創建一個自定義類型解決了這個問題實現IXmlSerializable接口:

假設您有一個帶有以下節點的XML對象:

<ItemOne>10</Item2> 
<ItemTwo /> 

的對象來表示它們:

public class MyItems { 
    [XmlElement("ItemOne")] 
    public int ItemOne { get; set; } 

    [XmlElement("ItemTwo")] 
    public CustomNullable<int> ItemTwo { get; set; } // will throw exception if empty element and type is int 
} 

動態可爲空的結構來表示任何潛在可空條目用轉換沿着

public struct CustomNullable<T> : IXmlSerializable where T: struct { 
    private T value; 
    private bool hasValue; 

    public bool HasValue { 
     get { return hasValue; } 
    } 

    public T Value { 
     get { return value; } 
    } 

    private CustomNullable(T value) { 
     this.hasValue = true; 
     this.value = value; 
    } 

    public XmlSchema GetSchema() { 
     return null; 
    } 

    public void ReadXml(XmlReader reader) { 
     string strValue = reader.ReadString(); 
     if (String.IsNullOrEmpty(strValue)) { 
      this.hasValue = false; 
     } 
     else { 
      T convertedValue = strValue.To<T>(); 
      this.value = convertedValue; 
      this.hasValue = true; 
     } 
     reader.ReadEndElement(); 

    } 

    public void WriteXml(XmlWriter writer) { 
     throw new NotImplementedException(); 
    } 

    public static implicit operator CustomNullable<T>(T value) { 
     return new CustomNullable<T>(value); 
    } 

} 

public static class ObjectExtensions { 
    public static T To<T>(this object value) { 
     Type t = typeof(T); 
     // Get the type that was made nullable. 
     Type valueType = Nullable.GetUnderlyingType(typeof(T)); 
     if (valueType != null) { 
      // Nullable type. 
      if (value == null) { 
       // you may want to do something different here. 
       return default(T); 
      } 
      else { 
       // Convert to the value type. 
       object result = Convert.ChangeType(value, valueType); 
       // Cast the value type to the nullable type. 
       return (T)result; 
      } 
     } 
     else { 
      // Not nullable. 
      return (T)Convert.ChangeType(value, typeof(T)); 
     } 
    } 
}