Deserialize XML element with xsi:nil="true" in C#

10,608

Solution 1

If you simply want to discard the xsi:nil attribute, set XmlElementAttribute.IsNullable = false in the [XmlElement] attribute on the property in the containing class that refers to location. E.g. if you have

[System.Xml.Serialization.XmlRootAttribute("locations", Namespace = "")]
public class LocationList
{
    [XmlElement("location", IsNullable = true)]
    public List<location> Locations { get; set; }
}

Manually change it to:

[System.Xml.Serialization.XmlRootAttribute("locations", Namespace = "")]
public class LocationList
{
    [XmlElement("location", IsNullable = false)]
    public List<location> Locations { get; set; }
}

If you want to bind to the value of xsi:nil while simultaneously binding to the other attribute values, in addition to setting IsNullable = false, you may add a manual property along the lines of the one shown in Can I have null attribute and other attribute at the same tag in XML created by XSD C# generated class?:

public partial class location
{
    bool nil;

    [XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
    public bool Nil
    {
        get
        {
            return nil;
        }
        set
        {
            nil = value;
        }
    }

    public bool ShouldSerializeNil() { return nil == true; }
}

Sample fiddle.

You could also pre- and post-process the XML to rename the xsi:nil attribute, as was suggested in this answer.

Solution 2

Try to use custom XmlReader. This approach may be useful when there is no possibility to change classes used for deserialization. Also it doesn't require extra memory consumption.

public class NilIgnoreReader : XmlTextReader
{
    public NilIgnoreReader(string url) : base(url) { }

    bool isLocation = false;

    public override bool Read()
    {
        bool read = base.Read();

        if (NodeType == XmlNodeType.Element && LocalName == "location")
            isLocation = true;

        return read;
    }

    public override string GetAttribute(string localName, string namespaceURI)
    {
        if (isLocation && localName == "nil" &&
            namespaceURI == "http://www.w3.org/2001/XMLSchema-instance")
        {
            isLocation = false;
            return "false";
        }
        return base.GetAttribute(localName, namespaceURI);
    }
}

The NilIgnoreReader return false for nil attribute bound to the http://www.w3.org/2001/XMLSchema-instance namespace from the location elements.

Usage

var xs = new XmlSerializer(typeof(Records));
Records record;

using (var reader = new NilIgnoreReader("test.xml"))
    record = (Records)xs.Deserialize(reader);

It works for XML like as

<?xml version="1.0"?>
<records xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="structure.xsd">
  <locations>
    <location country="PARAGUAY" city="Ciudad del Este" state="Alto Parana" xsi:nil="true"/>
    <location country="BRAZIL" city="Passo Fundo" state="Rio Grande do Sul" xsi:nil="true"/>
  </locations>
</records>

and classes like as

[XmlRoot("records")]
public class Records
{
    [XmlArray("locations")]
    [XmlArrayItem("location")]
    public Location[] Locations { get; set; }
}

public class Location
{
    [XmlAttribute("country")]
    public string Country { get; set; }
    [XmlAttribute("city")]
    public string City { get; set; }
    [XmlAttribute("state")]
    public string State { get; set; }
}
Share:
10,608
GigiSan
Author by

GigiSan

Updated on June 14, 2022

Comments

  • GigiSan
    GigiSan almost 2 years

    I'm trying to deserialize a XML file with XmlSerializer in C#.

    The destination class that follows was automatically generated using the xsd utility.

        [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
        [System.SerializableAttribute()]
        [System.Diagnostics.DebuggerStepThroughAttribute()]
        [System.ComponentModel.DesignerCategoryAttribute("code")]
        [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
        [System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = true)]
        public partial class location
        {
    
            private string cityField;
    
            private string countryField;
    
            private string stateField;
    
            private string textField;
    
            /// <remarks/>
            [System.Xml.Serialization.XmlAttributeAttribute()]
            public string city
            {
                get
                {
                    return this.cityField;
                }
                set
                {
                    this.cityField = value;
                }
            }
    
            /// <remarks/>
            [System.Xml.Serialization.XmlAttributeAttribute()]
            public string country
            {
                get
                {
                    return this.countryField;
                }
                set
                {
                    this.countryField = value;
                }
            }
    
            /// <remarks/>
            [System.Xml.Serialization.XmlAttributeAttribute()]
            public string state
            {
                get
                {
                    return this.stateField;
                }
                set
                {
                    this.stateField = value;
                }
            }
    
            /// <remarks/>
            [System.Xml.Serialization.XmlTextAttribute()]
            public string Text
            {
                get
                {
                    return this.textField;
                }
                set
                {
                    this.textField = value;
                }
            }
        }
    

    Everything works fine until I reach this portion of the file:

    <locations>
        <location country="PARAGUAY" city="Ciudad del Este" state="Alto Parana" xsi:nil="true"/>
        <location country="BRAZIL" city="Passo Fundo" state="Rio Grande do Sul" xsi:nil="true"/>
    </locations>
    

    As stated in the MSDN, an element with xsi:nil="true" will be deserialized as a null object, losing all attributes completely. In C# this translates in a null object.

    Is there a way to change this behaviour so to have the three properties deserialized?

    Thanks in advance for any advice!

    EDIT 1:

    This is the associated namespace:

    <records xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="structure.xsd">
        (location is within here somewhere)
    </records>