.net xml serialization nullable

.net - Deserializar el valor del atributo xml vacío en la propiedad nullable int utilizando XmlSerializer



serialization (4)

Últimamente he estado jugando con la serialización y he encontrado útiles los siguientes artículos y publicaciones cuando trato con datos nulos para los tipos de valor.

La respuesta a Cómo hacer que un tipo de valor sea anulable con XmlSerializer en C # - la serialización 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ó una buena respuesta . Alex también hizo una buena reseña en su blog sobre el problema que estaba tratando de resolver. Usando XmlSerializer para deserializar en un 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.

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''."}.

[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, la deserialización falla con InvalidOperationException:

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

¿Alguien puede aconsejar cómo deserializar el atributo con el valor vacío en el tipo anulable (como nulo) al mismo tiempo deserializar el valor del atributo no vacío en el 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)?

Actualización después del comentario de ahsteele:

  1. Atributo Xsi: nil

    Hasta donde sé, este atributo solo funciona con XmlElementAttribute: este atributo especifica que el elemento no tiene contenido, ya sean elementos secundarios o texto del cuerpo. Pero necesito encontrar la solución para XmlAttributeAttribute. De todos modos, no puedo cambiar xml porque no tengo control sobre él.

  2. bool * Propiedad especificada

    Esta propiedad solo funciona cuando el valor del atributo no está vacío o cuando falta el atributo. 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. Clase anulable personalizada como en esta publicación de Alex Scordellis

    Traté de adoptar la clase de esta publicación de blog para mi problema:

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

    Pero el constructor XmlSerializer falla con InvalidOperationException:

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

    XmlAttribute / XmlText no se puede usar para codificar tipos que implementan IXmlSerializable}

  4. Solución sustituta fea (lástima 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 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 implementar IXmlSerializable para toda la clase Element (es grande) y no hay una solución simple ...


Esto debería funcionar:

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


Pensé que también podría arrojar mi respuesta al sombrero: resolvió este problema creando un tipo personalizado que implementa la interfaz IXmlSerializable:

Supongamos que tiene un objeto XML con los siguientes nodos:

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

El objeto para 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 dinámica anulable para representar cualquier entrada potencialmente anulable 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)); } } }


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

Aquí está la muestra del 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); } }