Deserializing XML with DataContractSerializer
Asked Answered
B

2

17

I have a web service that returns the following data:

<?xml version=""1.0"" encoding=""UTF-8""?>
<RESPONSE>
    <KEY>12345</KEY>
    <PROPERTY>
        <PROPERTY_ADDRESS>
            <STREET_NUM>25</STREET_NUM>
            <STREET_ADDRESS>ELM ST</STREET_ADDRESS>
            <STREET_PREFIX/>
            <STREET_NAME>ELM</STREET_NAME>
            <STREET_TYPE>ST</STREET_TYPE>
            <STREET_SUFFIX/>
        </PROPERTY_ADDRESS>
    </PROPERTY>
</RESPONSE>

I have a class structure to match:

[DataContract(Name="RESPONSE", Namespace="")]
public class Response
{
    [DataMember(Name="KEY")]
    public string Key { get; set; }

    [DataMember(Name = "PROPERTY")]
    public Property Property { get; set; }
}

[DataContract(Name="PROPERTY", Namespace="")]
public class Property
{
    [DataMember(Name="PROPERTY_ADDRESS")]
    public PropertyAddress Address { get; set; }
}


[DataContract(Name="PROPERTY_ADDRESS", Namespace="")]
public class PropertyAddress
{
    [DataMember(Name="STREET_NUM")]
    public string StreetNumber { get; set; }

    [DataMember(Name = "STREET_ADDRESS")]
    public string StreetAddress { get; set; }

    [DataMember(Name = "STREET_PREFIX")]
    public string StreetPrefix { get; set; }

    [DataMember(Name = "STREET_NAME")]
    public string StreetName { get; set; }

    [DataMember(Name = "STREET_TYPE")]
    public string StreetType { get; set; }

    [DataMember(Name = "STREET_SUFFIX")]
    public string StreetSuffix { get; set; }
}

My deserialization code looks like this:

[Test]
public void TestMapping()
{
    var serializer = new DataContractSerializer(typeof(Response));

    Response response = null;

    using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(XmlData)))
    {
        response = (Response)serializer.ReadObject(ms);
    }

    //This works
    Assert.AreEqual("12345", response.Key);

    //This works
    Assert.AreEqual("25", response.Property.Address.StreetNumber);

    //This FAILS. StreetAddress is null
    Assert.AreEqual("ELM ST", response.Property.Address.StreetAddress);
}

For the life of me I can't figure out why StreetAddress is failing. It's got to be something simple that I'm missing.

Banzai answered 14/3, 2013 at 15:30 Comment(12)
what value are you getting in those property exactly, can you do a testcontext.writeline on each property to see the value.Guardant
response.Property.Address.StreetNumber == "25" response.Property.Address.StreetAddress == nullBanzai
Change StreetAddress to int to see if it's the property name or type failing.Oleaginous
The syntax of your XML is incorrect. The closing <PROPERTY_ADDRESS> tag is missing a /. If this is exactly what your service is returning, that's got to be throwing off deserialization.Lilley
@rickschott StreetAddress == 0Banzai
@SandraWalters: That was a typeo in my question but not in the actual dataBanzai
If you remove that property, does the next string property fail?Oleaginous
@rickschott yeah it fails too, but STREET_TYPE works just fine.Banzai
I getting the same results. The street address have a null valueGuardant
Street_Suffix bombs too if you give it an actual value.Extant
What's even more strange is that if you change the order of the xml (like move STREET_ADDRESS to the top) it magically deserializes that property correctly.Banzai
Possible duplicate of WCF DataContract DataMember order?Gaberdine
E
27

DataContractSerializer expects things to be in alphabetical order. You need to add Order to your Data Members for this to work correctly.

[DataContract(Name = "PROPERTY_ADDRESS", Namespace = "")]
public class PropertyAddress
{
    [DataMember(Name = "STREET_NUM", Order=0)]
    public string StreetNumber { get; set; }

    [DataMember(Name = "STREET_ADDRESS", Order=1)]
    public string StreetAddress { get; set; }

    [DataMember(Name = "STREET_PREFIX", Order=2)]
    public string StreetPrefix { get; set; }

    [DataMember(Name = "STREET_NAME", Order=3)]
    public string StreetName { get; set; }

    [DataMember(Name = "STREET_TYPE", Order=4)]
    public string StreetType { get; set; }

    [DataMember(Name = "STREET_SUFFIX",Order=5)]
    public string StreetSuffix { get; set; }
}
Extant answered 14/3, 2013 at 16:9 Comment(8)
@Micah: Its for performance reason. By expecting the tags in a certain order DataContractSerializer can save time on matching the contract's tags with the message tags. And there has to be some default order if none is specified explicitly.Joyejoyful
I think that any 'performance' gains are more than offset by the sheer amount of wasted developer time in trying to find, debug, and fix this silly requirement.Neilla
So... how do you disable this behavior?Pero
@Pero Not easily. The best thing at this point is to just set the Order property on the attribute. You can use IDispatchMessageInspector.AfterReceiveRequest to re-format the incoming request, or use a different serialization library.Extant
This is really frustrating, I just spent 3 hours figuring out that was the problem.Strength
@EranD. I also just spent about 3 hours before I finally came to find this out...FML. And Microsoft wonders why developers become disillusioned with their products.Rakel
I want to give an upvote for the accuracy and usefulness of your answer, but a downvote for the awful truth it contains. ;)Sparker
I spent hours to figure this hour. I was why the hell the compiler is behaving so discriminatory that reads all the other xml elements, except this newly added one. Thanks. [tears of joy on my face right now]Guzzle
J
7

You have to augment your data contract with the order of the elements because DataContractSerializer expects the elements to be sorted alphabetically by default. Which is not the case with your XML.

Here's the code:

[DataContract(Name = "PROPERTY_ADDRESS", Namespace = "")]
public class PropertyAddress
{
    [DataMember(Name = "STREET_NUM", Order=1)]
    public string StreetNumber { get; set; }

    [DataMember(Name = "STREET_ADDRESS", Order=2)]
    public string StreetAddress { get; set; }

    [DataMember(Name = "STREET_PREFIX", Order=3)]
    public string StreetPrefix { get; set; }

    [DataMember(Name = "STREET_NAME", Order=4)]
    public string StreetName { get; set; }

    [DataMember(Name = "STREET_TYPE", Order=5)]
    public string StreetType { get; set; }

    [DataMember(Name = "STREET_SUFFIX", Order=6)]
    public string StreetSuffix { get; set; }
}
Joyejoyful answered 14/3, 2013 at 16:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.