ApexEC
ApexEC

Reputation: 13

Getting null in nested elements when deserializing xml

I'm deserializing my xml which is string, into my classes. Problem is that part of nested child elements or their attributes return null.

Here is my deserialize function:

public static T DeSerialize<T>(this string xml)
{
    XmlSerializer serializer = new XmlSerializer(typeof(T));
    using (TextReader reader = new StringReader(xml))
    {
        T result = (T)serializer.Deserialize(reader);
        return result;
    }
}

Here are my classes:

[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.specifiedcompany.com/API")]
public partial class VideoGames
{
    private GameType[] typesField;

    private Platform platformField;

    private string memoField;

    [System.Xml.Serialization.XmlArray("Types", Order = 0)]
    [System.Xml.Serialization.XmlArrayItem("Data", IsNullable = false)]
    public GameType[] Types
    {
        get
        {
            return this.typesField;
        }
        set
        {
            this.typesField= value;
        }
    }

    [System.Xml.Serialization.XmlElementAttribute("Platform", Order = 1)]
    public Platform Platform
    {
        get
        {
            return this.platformField;
        }
        set
        {
            this.platformField= value;
        }
    }

    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string Memo
    {
        get
        {
            return this.memoField;
        }
        set
        {
            this.memoField= value;
        }
    }
}

[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.specifiedcompany.com/API")]
public partial class Platform
{
    private decimal compressedField;

    [System.Xml.Serialization.XmlAttributeAttribute()]
    public decimal Compressed
    {
        get
        {
            return this.compressedField;
        }
        set
        {
            this.compressedField= value;
        }
    }
}

[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.specifiedcompany.com/API")]
public partial class GameType
{
    private Code[] codeField;

    private string nameField;

    [System.Xml.Serialization.XmlArrayAttribute("Code", Order = 0)] 
    [System.Xml.Serialization.XmlArrayItemAttribute("Category", IsNullable = false)]
    public Code[] Code
    {
        get
        {
            return this.codeField;
        }
        set
        {
            this.codeField= value;
        }
    }

    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string Name
    {
        get
        {
            return this.nameField;
        }
        set
        {
            this.nameField= value;
        }
    }
}

[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.specifiedcompany.com/API")]
public partial class Code
{
    private string textField;

    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string Text
    {
        get
        {
            return this.textField;
        }
        set
        {
            this.textField= value;
        }
    }
}

Here is the xml:

<VideoGames Memo="1">
    <Types>
        <Data Name="RPG">
            <Code>
                <Category Text="TestingData"/>
            </Code>
        </Data>
    </Types>
    <Platform Compressed="3.2876"/>
</VideoGames>

And I'm using deserialize like this:

string xml = "";//xml string above.
VideoGames a = xml.DeSerialize<VideoGames>();

I expect every property in class will have value, but only Memo in VideoGames has value, others are null.

For example: a.Types is null, but a.Memo is "1".

I have tried all related questions searched by this topic, and none of them work.

Note:

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

I have checked this xDoc, it loaded perfectly, nothing was lost.

Upvotes: 1

Views: 2602

Answers (2)

Greg Quinn
Greg Quinn

Reputation: 2178

I ran into this same issue recently, where it turns out if a single field in a nested XML object was null or missing, or the value in the field couldn't deserialize to the specific type, the List representing the nested elements would just return null.

I tried all sorts of attributes for the fields, trying IsNullable and a bunch of other recommended attributes as mentioned here on stackoverflow, nothing helped.

To fix this, I ended up changing all the fields in my class the XML is being deserialized to type of string.

Hope this helps someone.

Upvotes: 0

dbc
dbc

Reputation: 116544

The easiest way to diagnose problems in XML deserialization is to serialize to XML and compare the observed XML with the required XML. Usually the problem will be immediately apparent.

If I create and serialize an instance of VideoGames as follows:

var root = new VideoGames
{
    Types = new []
    {
        new GameType
        {
            Name = "RPG",
            Code = new []
            {
                new Code { Text = "TestingData" },
            }
        },
    },
    Platform = new Platform  { Compressed = 3.2876m, },
};

var xml = root.GetXml(omitStandardNamespaces: true);
Console.WriteLine(xml);

Using the following extension method:

public static string GetXml<T>(this T obj, XmlSerializer serializer = null, bool omitStandardNamespaces = false)
{
    XmlSerializerNamespaces ns = null;
    if (omitStandardNamespaces)
    {
        ns = new XmlSerializerNamespaces();
        ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
    }           
    using (var textWriter = new StringWriter())
    {
        var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
        using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            (serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj, ns);
        return textWriter.ToString();
    }
}

Then the output is:

<VideoGames>
  <Types xmlns="http://www.specifiedcompany.com/API">
    <Data Name="RPG">
      <Code>
        <Category Text="TestingData" />
      </Code>
    </Data>
  </Types>
  <Platform Compressed="3.2876" xmlns="http://www.specifiedcompany.com/API" />
</VideoGames>

Demo fiddle here.

As you can see, the child elements <Types> and <Platform> are all serialized in an XML namespace, specifically http://www.specifiedcompany.com/API.

Your deserialization fails because, in the sample XML, these elements are not in any namespace.

Why is this happening? It's because all your classes including VideoGames have an XmlTypeAttribute attribute applied that specifies a Namespace:

[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.specifiedcompany.com/API")]
public partial class VideoGames
{
}

What this attribute does is to specify that by default, all properties of the type should be serialized into the specified namespace. (Compare with XmlRootAttribute which specifies how the root element <VideoGames> itself, rather than just its children, is to be serialized.)

If you don't want that, remove the XmlTypeAttribute.Namespace setting from your c# classes VideoGames, Platform, GameType and Code (demo fiddle #2 here). If you do want that, modify your XML to include the required namespace as shown in the output XML in this answer.

Upvotes: 4

Related Questions