La désérialisation xml vide la valeur de l'attribut dans nullable int propriété à l'aide de XmlSerializer

j'obtiens un xml du tiers et j'ai besoin de le desérialiser en objet C#. Ce xml peut contenir des attributs avec une valeur de type entier ou une valeur vide: attr="11" ou attr="". Je veux deserialiser cette valeur d'attribut dans la propriété avec le type d'entier nul. Mais XmlSerializer ne supporte pas la désérialisation en types nulles. Le code d'essai suivant échoue lors de la création de XmlSerializer avec InvalidOperationException {"il y avait une erreur reflétant le type "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);
    }
}

lorsque je change le type de propriété 'Value' en int, la desérialisation échoue avec InvalidOperationException:

il y a une erreur dans le document XML (1, 16).

est-ce que quelqu'un peut conseiller de désérialiser un attribut avec une valeur vide en type NULL (comme un null) en même temps que de désérialiser un attribut non vide dans l'entier? Est-il astuce pour cela je n'aurai pas à faire de la désérialisation de chaque champ manuellement (en fait il y en a beaucoup)?

mise à jour après le commentaire de ahsteele:

  1. xsi: néant attribut

    pour autant que je sache, cet attribut ne fonctionne qu'avec XmlElementAttribute - cet attribut spécifie que l'élément n'a pas de contenu, qu'il s'agisse d'éléments enfants ou de corps de texte. Mais j'ai besoin de trouver l' solution pour XmlAttributeAttribute. De toute façon, je ne peux pas changer xml parce que je n'ai aucun contrôle sur lui.

  2. bool *biens déterminés

    cette propriété ne fonctionne que lorsque la valeur de l'attribut n'est pas vide ou lorsque l'attribut est manquant. Quand attr a une valeur vide (attr=") le constructeur XmlSerializer échoue (comme prévu).

    public class Element
    {
        [XmlAttribute("attr")]
        public int Value { get; set; }
    
        [XmlIgnore]
        public bool ValueSpecified;
    }
    
  3. Classe Nullable comme dans ce billet de blog par Alex Scordellis

    j'ai essayé d'adopter la classe de ce billet de blog à mon problème:

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

    mais le constructeur XmlSerializer échoue avec InvalidOperationException:

    ne peut pas sérialiser la "valeur" d'un membre de la demande D'homologation de type.NullableInt.

    XmlAttribute/XmlText ne peut pas être utilisé pour encoder des types mettant en œuvre Ixmlserialisable }

  4. Laid solution de substitution (honte sur moi que j'ai écrit ce code ici :) ):

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

    mais je ne veux pas venir avec la solution comme ceci parce qu'il casse l'interface de ma classe pour ses consommateurs. Je ferais mieux d'implémenter manuellement l'interface Ixmlserialisable.

Actuellement, il semble que je dois mettre en œuvre Ixmlserialisable pour toute la classe de L'élément (il est grand) et il n'y a pas de solution simple...

64
demandé sur Community 2009-08-18 22:35:25

4 réponses

cela devrait fonctionner:

[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?); }
}
46
répondu abatishchev 2012-02-06 10:02:44

j'ai résolu ce problème en implémentant l'interface Ixmlserialisable. Je n'ai pas trouvé la voie plus facile.

voici le code d'essai échantillon:

[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);
    }
}
18
répondu Aliaksei Kliuchnikau 2009-08-20 09:18:27

j'ai moi-même beaucoup joué avec la sérialisation ces derniers temps et j'ai trouvé les articles et les messages suivants utiles pour traiter des données nulles pour les types de valeurs.

la réponse à La Comment créer une valeur de type nullable avec XmlSerializer en C# - sérialisation détails d'un joli truc astucieux de le XmlSerializer. Plus précisément, le XmlSerialier cherche une propriété booléenne de xxx spécifiée pour déterminer si elle devrait être inclus, ce qui vous permet d'ignorer les valeurs null.

Alex Scordellis a posé une question StackOverflow qui a reçu une bonne réponse . Alex a également fait un bon writeup sur son blog sur le problème qu'il essayait de résoudre en utilisant XmlSerializer pour deserialiser dans un nul .

la documentation MSDN sur Xsi:nil Attribute Binding Support est également utile. Comme la documentation sur Ixmlserialisable Interface , bien que l'écriture de votre propre implémentation devrait être votre dernier recours.

9
répondu ahsteele 2017-05-23 10:31:09

J'ai pensé que je pourrais aussi bien jeter ma réponse dans le chapeau: Résolu ce problème en créant un type personnalisé qui met en œuvre l'interface Ixmlserialisable:

dites que vous avez un objet XML avec les noeuds suivants:

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

l'objet pour les représenter:

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
}

Dynamique nullable struct pour représenter un potentiel nullable entrées avec conversion

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));
        }
    }
}
2
répondu Levi Fuller 2017-11-09 21:48:28