3

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.

ManoDestra
  • 6,325
  • 6
  • 26
  • 50
Dylan Liu
  • 123
  • 1
  • 7
  • What exactly is your question? – Athena Jul 22 '16 at 18:48
  • You have no XmlType annotation for your StateCounty class. I would expect to see something along the lines of `[XmlType("STATE-COUNTY")]`. If you write some serialization code to output some sample XML from your structure, using a test object with everything populated, then you'll probably see what's going wrong here, by comparing with your current XML. – ManoDestra Jul 22 '16 at 20:15

1 Answers1

1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340