2

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>
GigiSan
  • 1,170
  • 2
  • 19
  • 30
  • 1
    It's not well formed xml. The prefix `xsi` is not bound to any namespace. – Alexander Petrov Jun 24 '16 at 17:21
  • It has a specfication, I just omitted it for business privacy. I added a modified version. – GigiSan Jun 24 '16 at 17:24
  • 1
    Seems like a duplicate of [Can I have null attribute and other attribute at the same tag in XML created by XSD C# generated class?](https://stackoverflow.com/questions/32903839/can-i-have-null-attribute-and-other-attribute-at-the-same-tag-in-xml-created-by) – dbc Jun 24 '16 at 17:42
  • Not really. I'm trying to DEserialize while he's trying to serialize. But thanks for pointing it out. – GigiSan Jun 24 '16 at 17:50
  • @dbc you were provvidential. I took advice from that answer to come up with a solution. I will post it as soon as I crawl out of this mud pool. >_< Thanks a lot! – GigiSan Jun 24 '16 at 18:11

2 Answers2

4

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • This is exactly the idea I had and that I mentioned in the comments, but it seems to break when it encounters a `` without a xsi:nil, setting null for all subsequent ``s. – GigiSan Jun 24 '16 at 19:01
  • @GigiSan - can you edit your question to include a [mcve] for the problem you're now seeing? I updated my [fiddle](https://dotnetfiddle.net/LQOZDQ) and I'm not seeing it. – dbc Jun 24 '16 at 19:08
  • sorry for the long wait. I checked the changes I made in the code and found out that I had put `IsNullable = false` on the `Location` class instead of the `Locations` list (actually seemed more logical to me). I also added that `ShouldSerializeNil()` method but did not understand it clearly, is it a standard method of serialization? Anyway, thanks for the help. Since this is similar to what I did and already knew that it could work, I'm flagging this one as answer. – GigiSan Jun 27 '16 at 08:21
  • 1
    @GigiSan - the `ShouldSerializeNil()` prevents the [`xsi:nil`](https://www.w3.org/TR/xmlschema-1/#xsi_nil) attribute from being serialized unless the value is `true`. Emitting a false value is redundant and generally not done. As to why it works, see [here](https://stackoverflow.com/questions/37838640/shouldserialize-vs-specified-conditional-serialization-pattern/37842985#37842985). – dbc Jun 27 '16 at 08:29
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; }
}
Alexander Petrov
  • 13,457
  • 2
  • 20
  • 49
  • This actually works but being the program a very delicate process I need to do some more tests before deploying it. I will let you know soon. Thanks meanwhile! – GigiSan Jun 24 '16 at 19:06
  • @GigiSan - Yes, of course, test it thoroughly. I'm not sure about the full correctness of this approach. – Alexander Petrov Jun 24 '16 at 19:11
  • I chose to go with dbc's answer cause it was close to what I had in mind already and was almost sure of the result. Given the short time I had I could not quickly test your code with all cases. But anyway it did work in the first cases I tried and I think both solution are equally effective. So thank you very much for your effort. :) – GigiSan Jun 27 '16 at 08:25