2009-08-18 11 views
60

Ricevo un xml dal terzo utente e devo deserializzare nell'oggetto C#. Questo xml può contenere attributi con valore di tipo intero o valore vuoto: attr = "11" o attr = "". Voglio deserializzare questo valore di attributo nella proprietà con il tipo di numero nullable intero. Ma XmlSerializer non supporta la deserializzazione in tipi nullable. Il seguente codice di test non riesce durante la creazione di XmlSerializer con InvalidOperationException {"Si è verificato un errore che riflette il tipo 'TestConsoleApplication.SerializeMe'."}.Deserializzazione del valore dell'attributo xml vuoto nella proprietà int nullo utilizzando XmlSerializer

[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); 
    } 
} 

Quando cambio tipo di proprietà 'Valore' a int, deserializzazione non riesce con InvalidOperationException:

C'è un errore nel documento XML (1, 16).

Qualcuno può consigliare come deserializzare attributo con valore vuoto in tipo annullabile (come null) Allo stesso tempo deserializzazione valore di attributo non vuota in un numero intero? C'è qualche trucco per questo, quindi non dovrò fare manualmente la deserializzazione di ogni campo (in realtà ce ne sono molti)?

Aggiornamento dopo il commento da ahsteele:

  1. Xsi:nil attribute

    Per quanto ne so, questo attributo funziona solo con XmlElementAttribute - Questo attributo specifica che l'elemento non ha contenuto, se gli elementi figlio o corpo testo. Ma ho bisogno di trovare la soluzione per XmlAttributeAttribute. Ad ogni modo non posso cambiare xml perché non ne ho il controllo.

  2. bool *Specified property

    Questa proprietà funziona solo quando il valore di attributo non è vuoto o quando l'attributo è mancante. Quando attr ha valore vuoto (attr = '') il costruttore di XmlSerializer fallisce (come previsto).

    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

    ho cercato di adottare la classe da questo post del blog al mio problema:

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

    Ma XmlSerializer costruttore fallisce con InvalidOperationException:

    Non può serializzare membro 'Valore' di tipo TestConsoleApplication.NullableInt.

    XmlAttribute/XmlText non può essere usato per codificare i tipi di applicazione IXmlSerializable}

  4. soluzione surrogato Ugly (mannaggia a me che ho scritto questo codice qui :)):

    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; 
        } 
    } 
    

    Ma don Voglio inventare la soluzione in questo modo perché rompe l'interfaccia della mia classe per i suoi consumatori. Sarebbe meglio implementare manualmente l'interfaccia IXmlSerializable.

Attualmente sembra che devo implementare IXmlSerializable per tutta la classe Element (è grande) e non ci sono semplici soluzione ...

risposta

17

Ho risolto questo problema implementando l'interfaccia IXmlSerializable. Non ho trovato il modo più semplice.

Ecco il codice di test di esempio:

[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

Sono stato nei guai con la serializzazione molto me stesso di ritardo e hanno trovato utili i seguenti articoli e post quando si tratta di dati nulli per i tipi di valore.

La risposta a How to make a value type nullable with XmlSerializer in C# - serialization descrive un trucco piuttosto elegante di XmlSerializer. In particolare, XmlSerialier cerca una proprietà booleana XXXSpecified per determinare se deve essere inclusa e consente di ignorare i valori null.

Alex Scordellis ha chiesto una domanda StackOverflow che ha ricevuto a good answer. Alex ha anche scritto bene sul suo blog sul problema che stava cercando di risolvere Using XmlSerializer to deserialize into a Nullable<int>.

Anche la documentazione MSDN su Xsi:nil Attribute Binding Support è utile. Come è la documentazione su IXmlSerializable Interface, anche se scrivere la propria implementazione dovrebbe essere l'ultima risorsa.

+0

Il "Uso XmlSerializer per deserializzare in un Nullable" collegamento è guasto . [Ecco una versione cache di google] (http://webcache.googleusercontent.com/search?q=cache:vT5GiyOCWyIJ:www.rqna.net/qna/zzrzt-deserialise-missing-xml-attribute-to-nullable-type .html) – Anttu

+0

@Anttu Ho cambiato il link nella risposta all'archivio Wayback Machine dell'originale * Usando XmlSerializer per deserializzare in un Nullable *. – ahsteele

43

Questo dovrebbe funzionare:

[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

Questo funzionerà, ma questa è la stessa soluzione del numero 4) dalla mia domanda. Non voglio introdurre campi surrogati nell'interfaccia pubblica della mia classe. Grazie a –

+4

FWIW, trovo questa soluzione migliore rispetto all'implementazione esplicita di IXmlSerializable (la soluzione accettata), sebbene non alla domanda specifica dell'OP. Evito di implementare IXmlSerializable a meno che non sia assolutamente necessario, trovando che si finisce per costare di più nella manutenzione nel lungo periodo. In un caso semplice come questo e senza altri fattori attenuanti, andrò per la "brutta" soluzione surrogata senza pensarci due volte. –

2

pensato che avrei potuto anche gettare la mia risposta nel cappello: risolto questo problema creando un tipo personalizzato che implementa l'interfaccia IXmlSerializable:

Supponiamo di avere un oggetto XML con i seguenti nodi:

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

L'oggetto per rappresentare loro:

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 
} 

dinamico struct annullabile per rappresentare eventuali voci annullabili insieme conversione

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)); 
     } 
    } 
} 
Problemi correlati