Desserializando o valor do atributo xml vazio na propriedade int anulável usando XmlSerializer

Eu recebo um xml do terceiro e preciso desserializá-lo em object C #. Este xml pode conter atributos com valor do tipo inteiro ou valor vazio: attr = ”11” ou attr = ””. Eu quero desserializar esse valor de atributo para a propriedade com o tipo de inteiro anulável. Mas XmlSerializer não oferece suporte a desserialização em tipos anuláveis. O código de teste a seguir falha durante a criação do XmlSerializer com InvalidOperationException {“Ocorreu um erro ao refletir o 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 = "valE"; var deserializer = new XmlSerializer(typeof(SerializeMe)); Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml)); var result = (SerializeMe)deserializer.Deserialize(xmlStream); } } 

Quando altero o tipo de propriedade ‘Value’ para int, a desserialização falha com InvalidOperationException:

Há um erro no documento XML (1, 16).

Alguém pode aconselhar como desserializar o atributo com valor vazio em tipo anulável (como um nulo) ao mesmo tempo desserializando o valor do atributo não vazio no número inteiro? Existe algum truque para isso, então não terei que fazer a desserialização de cada campo manualmente (na verdade, há muitos deles)?

Atualização após comentário de ahsteele:

  1. Atributo Xsi: nil

    Tanto quanto eu sei, este atributo funciona apenas com XmlElementAttribute – este atributo especifica que o elemento não tem conteúdo, se elementos filho ou texto do corpo. Mas eu preciso encontrar a solução para XmlAttributeAttribute. De qualquer forma eu não posso mudar xml porque eu não tenho controle sobre isso.

  2. bool * Propriedade especificada

    Essa propriedade funciona apenas quando o valor do atributo não está vazio ou quando o atributo está ausente. Quando attr tem valor vazio (attr = ”) o construtor XmlSerializer falha (conforme esperado).

     public class Element { [XmlAttribute("attr")] public int Value { get; set; } [XmlIgnore] public bool ValueSpecified; } 
  3. Classe Nullable personalizada como neste post de Alex Scordellis

    Eu tentei adotar a class deste blog para o meu problema:

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

    Mas o construtor XmlSerializer falha com InvalidOperationException:

    Não é possível serializar o membro ‘Valor’ do tipo TestConsoleApplication.NullableInt.

    XmlAttribute / XmlText não pode ser usado para codificar tipos implementando IXmlSerializable}

  4. Solução substituta feia (pena que escrevi este código aqui :)):

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

    Mas eu não quero chegar a uma solução como essa porque quebra a interface da minha class para seus consumidores. Eu seria melhor implementar manualmente a interface IXmlSerializable.

Atualmente parece que eu tenho que implementar IXmlSerializable para toda a class Element (é grande) e não há uma solução simples…

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

Eu resolvi esse problema implementando a interface IXmlSerializable. Eu não encontrei maneira mais fácil.

Aqui está o exemplo de código de teste:

 [XmlRoot("root")] public class DeserializeMe { [XmlArray("elements"), XmlArrayItem("element")] public List 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(attr1); Value2 = ConvertToNullable(attr2); } private static T? ConvertToNullable(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 = @""; XmlSerializer deserializer = new XmlSerializer(typeof(DeserializeMe)); Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml)); var result = (DeserializeMe)deserializer.Deserialize(xmlStream); } } 

Eu tenho me envolvido muito com a serialização ultimamente e descobri que os artigos e posts a seguir são úteis quando se lida com dados nulos para tipos de valor.

A resposta para Como tornar um tipo de valor anulável com XmlSerializer em serialização C # detalha um truque bem bacana do XmlSerializer. Especificamente, o XmlSerialier procura uma propriedade booleana XXXSpecified para determinar se ela deve ser incluída, o que permite que você ignore nulos.

Alex Scordellis fez uma pergunta no StackOverflow que recebeu uma boa resposta . Alex também fez um bom artigo em seu blog sobre o problema que estava tentando resolver Usando o XmlSerializer para desserializar em um Nullable .

A documentação do MSDN no Xsi: nil suporte de vinculação de atributo também é útil. Como é a documentação na Interface IXmlSerializable , apesar de escrever sua própria implementação deve ser seu último recurso.

Pensei que poderia jogar minha resposta no chapéu: Resolvi esse problema criando um tipo personalizado que implementa a interface IXmlSerializable:

Digamos que você tenha um object XML com os seguintes nós:

 10  

O object para representá-los:

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

Estrutura anulável dinâmica para representar possíveis inputs anuláveis ​​juntamente com a conversão

 public struct CustomNullable : 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(); this.value = convertedValue; this.hasValue = true; } reader.ReadEndElement(); } public void WriteXml(XmlWriter writer) { throw new NotImplementedException(); } public static implicit operator CustomNullable(T value) { return new CustomNullable(value); } } public static class ObjectExtensions { public static T To(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)); } } }