2009-08-18 20 views
60

Obtengo un xml del tercero y necesito deserializarlo en el objeto C#. Este xml puede contener atributos con valor de tipo entero o valor vacío: attr = "11" o attr = "". Quiero deserializar este valor de atributo en la propiedad con el tipo de entero nullable. Pero XmlSerializer no admite la deserialización en tipos anulables. El siguiente código de prueba falla durante la creación de XmlSerializer con InvalidOperationException {"Hubo un error que refleja el tipo 'TestConsoleApplication.SerializeMe'."}.Deserializar el valor del atributo xml vacío en la propiedad int nullable utilizando 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); 
    } 
} 

Cuando cambio el tipo de propiedad 'valor' a int, deserialización falla con InvalidOperationException:

Hay un error en el documento XML (1, 16).

Puede alguien aconsejar cómo deserializar atributo con valor vacío en tipo anulable (como null) al mismo tiempo deserializar valor de atributo no vacía en el número entero? ¿Hay algún truco para esto así que no tendré que hacer la deserialización de cada campo manualmente (de hecho hay muchos de ellos)?

Update después de comentario de ahsteele:

  1. Xsi:nil attribute

    Por lo que yo sé, este atributo sólo funciona con XmlElementAttribute - este atributo especifica que el elemento no tiene contenido, si los elementos o el cuerpo del niño texto. Pero necesito encontrar la solución para XmlAttributeAttribute. De todos modos, no puedo cambiar xml porque no tengo control sobre él.

  2. bool *Specified property

    Esta propiedad sólo funciona cuando el valor del atributo no está vacío o cuando el atributo no se encuentra. Cuando attr tiene un valor vacío (attr = '') el constructor XmlSerializer falla (como se esperaba).

    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

    Me trataron de adoptar la clase de esta entrada del blog a mi problema:

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

    Pero XmlSerializer constructor falla con InvalidOperationException:

    No se puede serializar miembro 'Valor' de tipo TestConsoleApplication.NullableInt.

    XmlAttribute/xmltext no puede ser utilizado para codificar los tipos de aplicación IXmlSerializable}

  4. solución sustituta feo (que pena por mí que escribí este código aquí :)):

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

    Pero Don No quiero llegar a una solución como esta porque rompe la interfaz de mi clase para sus consumidores. Sería mejor implementar manualmente la interfaz IXmlSerializable.

Actualmente parece que tengo que poner en práctica IXmlSerializable para toda la clase de elemento (que es grande) y no hay solución simple ...

Respuesta

17

Resolví este problema implementando la interfaz IXmlSerializable. No encontré la manera más fácil.

Aquí está el ejemplo de código de prueba:

[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

He estado jugando con la serialización mucho a mí mismo en los últimos tiempos y ha encontrado útiles los siguientes artículos y publicaciones cuando se trata de datos nulos para los tipos de valores.

La respuesta a How to make a value type nullable with XmlSerializer in C# - serialization detalla un truco muy ingenioso del XmlSerializer. Específicamente, el XmlSerialier busca una propiedad booleana XXXSpecified para determinar si debe incluirse, lo que le permite ignorar los nulos.

Alex Scordellis hizo una pregunta sobre StackOverflow que recibió a good answer. Alex también hizo una buena crítica en su blog sobre el problema que estaba tratando de resolver Using XmlSerializer to deserialize into a Nullable<int>.

La documentación de MSDN en Xsi:nil Attribute Binding Support también es útil. Como es la documentación en IXmlSerializable Interface, aunque escribir su propia implementación debería ser su último recurso.

+0

El "Uso de XmlSerializer para deserializar en un anulable" enlace está muerto . [Aquí hay una versión en caché de 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 Cambié el enlace en la respuesta al archivo Wayback Machine del original * Usando XmlSerializer para deserializar en un Nullable *. – ahsteele

43

Esto debería funcionar:

Pensamiento
[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

Esto funcionará, pero esta es la misma solución que el número 4) de mi pregunta. No quiero presentar campos secundarios en la interfaz pública de mi clase. Gracias –

+4

FWIW, considero que esta solución es mejor que la implementación explícita de IXmlSerializable (la solución aceptada), aunque no para la pregunta específica de OP. Evito implementar IXmlSerializable a menos que sea absolutamente necesario, descubriendo que me termina costando más en mantenimiento a largo plazo. En un caso simple como este y sin ningún otro factor mitigante, buscaré la solución sustituta "fea" sin pensarlo dos veces. –

2

que también podría tirar mi respuesta en el sombrero: solucionado este problema mediante la creación de un tipo personalizado que implementa la interfaz IXmlSerializable:

Supongamos que tiene un objeto XML con los siguientes nodos:

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

El objeto de representarlos:

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 
} 

estructura anulable dinámico para representar cualquier entrada anulables potenciales junto con la conversión

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)); 
     } 
    } 
} 
Cuestiones relacionadas