Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XML deserialization with array inside a class

I'm trying to deserialize a string of XML data. But I got back no data in the object returned by the deserialization function.

Here are the class definitions:

[XmlType("STATE-COUNTY-RECORDS")]
public class StateCountyRecords
{
    [XmlElement("TOTAL")]
    public int Total { get; set; }

    [XmlElement("LIMIT")]
    public int Limit { get; set; }

    [XmlElement("OFFSET")]
    public int Offset { get; set; }

    [XmlArray("STATE-COUNTY-ARRAY"), XmlArrayItem("STATE-COUNTY")]
    public StateCounty[] StateCountyArray { get; set; }

    public StateCountyRecords() {}

    public StateCountyRecords(int total, int limit, int offset, int[] id, string[] fips_code, string[] state, string[] name)
    {
        Total = total;
        Limit = limit;
        Offset = offset;

        var count = id.Length;
        StateCountyArray = new StateCounty[count];
        for (int i = 0; i < count; i++)
        {
            StateCountyArray[i] = new StateCounty(id[i], fips_code[i], state[i], name[i]);
        }
    }
}

public class StateCounty
{
    [XmlElement("ID")]
    public int Id { get; set; }

    [XmlElement("FIPS-CODE")]
    public string Fips_Code { get; set; }

    [XmlElement("STATE")]
    public string State { get; set; }

    [XmlElement("NAME")]
    public string Name { get; set; }

    public StateCounty(int id, string fips_code, string state, string name)
    {
        Id = id;
        Fips_Code = fips_code;
        State = state;
        Name = name;
    }

    public StateCounty() { }
}

Here is the xml string data:

<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<STATE-COUNTY-FILE xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<STATE-COUNTY-RECORDS>
    <TOTAL>3314</TOTAL>
    <LIMIT>10</LIMIT>
    <OFFSET>0</OFFSET>
    <STATE-COUNTY-ARRAY>
    <STATE-COUNTY>
        <ID>1</ID>
        <FIPS-CODE>01000</FIPS-CODE>
        <STATE>AL</STATE>
        <NAME>Alabama</NAME>
    </STATE-COUNTY>
    <STATE-COUNTY>
        <ID>2</ID>
        <FIPS-CODE>01001</FIPS-CODE>
        <STATE>AL</STATE>
        <NAME>Autauga</NAME>
    </STATE-COUNTY>
    <STATE-COUNTY>
        <ID>3</ID>
        <FIPS-CODE>01003</FIPS-CODE>
        <STATE>AL</STATE>
        <NAME>Baldwin</NAME>
    </STATE-COUNTY>
    </STATE-COUNTY-ARRAY>
</STATE-COUNTY-RECORDS>
</STATE-COUNTY-FILE>

Here is the function to deserialize the xml string:

    public static StateCountyRecords DeserializeStateCountyData(string xmlString)
    {
        XmlSerializer mySerializer = new XmlSerializer(typeof(StateCountyRecords), new XmlRootAttribute("STATE-COUNTY-FILE"));
        using (XmlReader myXmlReader = XmlReader.Create(new StringReader(xmlString)))
        {
            StateCountyRecords rslt;
            rslt = (StateCountyRecords)mySerializer.Deserialize(myXmlReader);
        }
    }
}

The rslt I got back contains no data with Total and Limit, and Offset all being 0 and StateCountyArray being null. I didn't get any error. I made sure that the xml is valid by:

if (myXmlReader.CanResolveEntity && myXmlReader.CanReadValueChunk && myXmlReader.CanReadBinaryContent)
                rslt = (StateCountyRecords)mySerializer.Deserialize(myXmlReader);

I wonder if there is a mismatch between the XmlElement of my class definition and the XML file.

like image 919
Dylan Liu Avatar asked Feb 11 '26 11:02

Dylan Liu


1 Answers

Your problem is that your XML has an outer element that is not accounted for in your data model, namely the <STATE-COUNTY-FILE> element:

<STATE-COUNTY-FILE xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <STATE-COUNTY-RECORDS>
    <!-- Various elements under StateCountyRecords -->
  </STATE-COUNTY-RECORDS>
</STATE-COUNTY-FILE>

Since the root of your data model is StateCountyRecords there is nothing that corresponds to this outermost element. You try to solve this by using new XmlRootAttribute("STATE-COUNTY-FILE"), however this will only rename the outer element, not add the extra level of nesting.

Instead, you need to introduce an outer container POCO for this purpose:

[XmlType("STATE-COUNTY-FILE")]
public class StateCountyFile
{
    [XmlElement("STATE-COUNTY-RECORDS")]
    public StateCountyRecords StateCountyRecords { get; set; }
}

And deserialize as follows:

    public static StateCountyRecords DeserializeStateCountyData(string xmlString)
    {
        var mySerializer = new XmlSerializer(typeof(StateCountyFile));
        using (var myXmlReader = XmlReader.Create(new StringReader(xmlString)))
        {
            var rslt = (StateCountyFile)mySerializer.Deserialize(myXmlReader);
            return rslt.StateCountyRecords;
        }
    }

Alternatively, you could manually advance the XmlReader until encountering an element with the name <STATE-COUNTY-RECORDS>, and deserialize that:

    public static StateCountyRecords DeserializeStateCountyData(string xmlString)
    {
        return XmlTypeExtensions.DeserializeNestedElement<StateCountyRecords>(xmlString);
    }

Using the extension methods:

public static class XmlTypeExtensions
{
    public static T DeserializeNestedElement<T>(string xmlString)
    {
        var mySerializer = new XmlSerializer(typeof(T));
        var typeName = typeof(T).XmlTypeName();
        var typeNamespace = typeof(T).XmlTypeNamespace();

        using (var myXmlReader = XmlReader.Create(new StringReader(xmlString)))
        {
            while (myXmlReader.Read())
                if (myXmlReader.NodeType == XmlNodeType.Element && myXmlReader.Name == typeName && myXmlReader.NamespaceURI == typeNamespace)
                {
                    return (T)mySerializer.Deserialize(myXmlReader);
                }
            return default(T);
        }
    }

    public static string XmlTypeName(this Type type)
    {
        var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
        if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
            return xmlType.TypeName;
        return type.Name;
    }

    public static string XmlTypeNamespace(this Type type)
    {
        var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
        if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
            return xmlType.Namespace;
        return string.Empty;
    }
}

Incidentally, deserialization problems such as this are easily diagnosed by creating an instance of your class in memory, serializing it, then comparing the resulting XML with the input XML. Usually the problem is apparent.

like image 93
dbc Avatar answered Feb 14 '26 05:02

dbc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!