86 Stimmen

Deserialisierung leerer Xml-Attributwerte in nullbare int-Eigenschaften mit XmlSerializer

Ich erhalte eine Xml von der 3. Partei und ich muss es in C#-Objekt zu deserialisieren. Diese Xml kann Attribute mit Wert von Integer-Typ oder leeren Wert enthalten: attr="11" oder attr="". Ich möchte dieses Attribut Wert in die Eigenschaft mit dem Typ der nullable Integer zu deserialisieren. Aber XmlSerializer unterstützt keine Deserialisierung in nullable Typen. Der folgende Testcode schlägt bei der Erstellung von XmlSerializer mit InvalidOperationException {"There was an error reflecting type 'TestConsoleApplication.SerializeMe'."} fehl.

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

Wenn ich den Typ der Eigenschaft "Value" in int ändere, schlägt die Deserialisierung mit InvalidOperationException fehl:

Es gibt einen Fehler im XML-Dokument (1, 16).

Kann jemand beraten, wie zu deserialize Attribut mit leeren Wert in nullable Typ (als null) zur gleichen Zeit deserializing nicht leeren Attributwert in die Ganzzahl? Gibt es einen Trick dafür, so dass ich nicht zu tun haben, Deserialisierung jedes Feldes manuell (tatsächlich gibt es eine Menge von ihnen)?

Update nach Kommentar von ahsteele:

  1. Xsi:nil-Attribut

    Soweit ich weiß, funktioniert dieses Attribut nur mit XmlElementAttribute - dieses Attribut gibt an, dass das Element keinen Inhalt hat, weder Kindelemente noch Textkörper. Aber ich muss die Lösung für XmlAttributeAttribute finden. Auf jeden Fall kann ich xml nicht ändern, weil ich keine Kontrolle darüber habe.

  2. bool *Bestimmte Eigenschaft

    Diese Eigenschaft funktioniert nur, wenn der Attributwert nicht leer ist oder wenn das Attribut fehlt. Wenn attr einen leeren Wert hat (attr=''), schlägt der XmlSerializer-Konstruktor (wie erwartet) fehl.

    public class Element
    {
        [XmlAttribute("attr")]
        public int Value { get; set; }
    
        [XmlIgnore]
        public bool ValueSpecified;
    }
  3. Benutzerdefinierte nullbare Klasse wie in diesem Blogbeitrag von Alex Scordellis

    Ich habe versucht, die Klasse aus diesem Blogbeitrag auf mein Problem anzuwenden:

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

    Aber XmlSerializer Konstruktor schlägt mit InvalidOperationException:

    Das Mitglied 'Value' vom Typ TestConsoleApplication.NullableInt kann nicht serialisiert werden.

    XmlAttribute/XmlText können nicht verwendet werden, um Typen zu kodieren, die IXmlSerializable implementieren }

  4. Hässliche Surrogatlösung (Schande über mich, dass ich diesen Code hier geschrieben habe :) ):

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

    Aber ich möchte nicht mit der Lösung wie diese kommen, weil es bricht Schnittstelle meiner Klasse für seine Verbraucher. Ich würde besser manuell implementieren IXmlSerializable Schnittstelle.

Derzeit sieht es aus wie ich IXmlSerializable für die gesamte Element-Klasse (es ist groß) zu implementieren und es gibt keine einfachen Workaround

1 Stimmen

Dieses Problem plagt mich schon seit einem Jahrzehnt. Gibt es keinen einfachen Dekorator, der dieses Problem im Jahr 2021 löst?

72voto

Das sollte funktionieren:

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

24voto

Aliaksei Kliuchnikau Punkte 13329

Ich habe dieses Problem durch die Implementierung der Schnittstelle IXmlSerializable gelöst. Ich habe keinen einfacheren Weg gefunden.

Hier ist das Testcode-Beispiel:

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

14voto

ahsteele Punkte 25888

Ich habe in letzter Zeit viel mit der Serialisierung herumgespielt und fand die folgenden Artikel und Beiträge hilfreich beim Umgang mit Null-Daten für Werttypen.

Die Antwort auf Wie man einen Werttyp mit XmlSerializer in C# nullbar macht - Serialisierung beschreibt einen ziemlich raffinierten Trick des XmlSerializer. Insbesondere sucht der XmlSerialier nach einer booleschen XXXSpecified-Eigenschaft, um festzustellen, ob sie eingeschlossen werden soll, was es Ihnen ermöglicht, Nullen zu ignorieren.

Alex Scordellis stellte eine StackOverflow-Frage, die mit eine gute Antwort . Alex hat in seinem Blog auch einen guten Bericht über das Problem geschrieben, das er zu lösen versuchte Verwendung von XmlSerializer zur Deserialisierung in ein Nullable<int> .

Die MSDN-Dokumentation zu Unterstützung von Xsi:nil-Attributbindungen ist ebenfalls nützlich. Ebenso wie die Dokumentation über IXmlSerializable Schnittstelle Eine eigene Implementierung zu schreiben, sollte jedoch der letzte Ausweg sein.

3voto

Levi Fuller Punkte 11810

Ich dachte, ich könnte auch meine Antwort in den Hut werfen: Ich habe dieses Problem gelöst, indem ich einen benutzerdefinierten Typ erstellt habe, der die Schnittstelle IXmlSerializable implementiert:

Angenommen, Sie haben ein XML-Objekt mit den folgenden Knoten:

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

Das Objekt, das sie repräsentiert:

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
}

Dynamische löschbare Struktur zur Darstellung aller potenziell löschbaren Einträge zusammen mit der Konvertierung

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

2voto

ErdemS Punkte 37

Sie können dies auch tun, indem Sie die xml in eine XmlDocument und deserialisiert diese dann in Json um das Objekt zu erhalten T nach dem Sie suchen.

        public static T XmlToModel<T>(string xml)
        {

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);

            string jsonText = JsonConvert.SerializeXmlNode(doc);

            T result = JsonConvert.DeserializeObject<T>(jsonText);

            return result;
        }

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X