A simple cheat sheet that has some examples of how to go from Xml to C# object using Xml Data Annotations.
There is a simple way to do this using visual studio if you did not know you can use the “Paste Special” feature. Copy your xml so its on your clipboard. Then in VS (I’m using 2017) go to “Edit” -> “Paste Special” -> “Paste Xml as Classes”. This works just fine and is a good quick starting point but I’ve never had good luck with this feature as far as it choosing the correct data types and the naming of the objects always drives me crazy. If you want to know what I’m talking about go ahead and give it a try.
Below is the sample of the xml that we will be deserializing into C# objects. You will need to add System.Xml.Serialization.dll to your project as that is where the definitions for these annotations are defined.
<customerorder>
<name>
<first>John</first>
<middle></middle>
<last>Smith</last>
</name>
<contactinfo email="jsmith@fake.com">
<phone>9998675309</phone>
</contactinfo>
<address isappartment="false">
<street>123 tiger lane</street>
<city>Make Believe</city>
<zip>11111</zip>
</address>
<orders>
<order id="sfswesfy64165">soccor ball</order>
<order id="ssefoofkgh8689">baseball bat</order>
<order id="iiefsefs">soccor net</order>
<order id="owieyfwejif">baseball glove</order>
</orders>
</customerorder>
First lets create an object that maps to the <name> element.
<name>
<first>John</first>
<middle></middle>
<last>Smith</last>
</name>
public class CustomerName
{
[XmlElement(ElementName = "first")]
public string FirstName { get; set; }
[XmlElement(ElementName = "middle")]
public string MiddleName { get; set; }
[XmlElement(ElementName = "last")]
public string LastName { get; set; }
}
In this case we need the XmlElement(ElementName = “”) annotation because the element name differs from what the public property name is.
Any time the element name differs from the property name you will need a annotation to ensure that it gets mapped correctly.
Lets move on to the <contactinfo> <address> and <orders> elements. We will start with the first two mentioned as <orders> case is one that I see a lot but requires an extra step.
<contactinfo email="jsmith@fake.com">
<phone>9998675309</phone>
</contactinfo>
<address isappartment="false">
<street>123 tiger lane</street>
<city>Make Believe</city>
<zip>11111</zip>
</address>
In the CustomerContact class we see the XmlAttribute annotation being used to map “email” to the public property EmailAddress. We see this again in the CustomerAddress class for the “isappartment” attribute. We also see in the CustomerAddress class that the data type for IsAppartment property is a bool, the .NET Xml Serialization process will determine that value at runtime.
public class CustomerContact
{
[XmlAttribute(AttributeName = "email")]
public string EmailAddress { get; set; }
[XmlElement(ElementName = "phone")]
public string PhoneNumber { get; set; }
}
public class CustomerAddress
{
[XmlAttribute(AttributeName = "isappartment")]
public bool IsAppartment { get; set; }
[XmlElement(ElementName = "street")]
public string Street { get; set; }
[XmlElement(ElementName = "city")]
public string City { get; set; }
[XmlElement(ElementName = "zip")]
public string Zip { get; set; }
}
Okay before moving onto the last element lets tie all the ones that we have so far together. We need the root element/object to hold our subelements/classes. We will decorate the root class with the XmlRoot annotation because our class name differs from the root element name.
[XmlRoot(ElementName = "customerorder")]
public class CustomerOrder
{
[XmlElement(ElementName = "name")]
public CustomerName CustomerName { get; set; }
[XmlElement(ElementName = "contactinfo")]
public CustomerContact ContactInfo { get; set; }
[XmlElement(ElementName = "address")]
public CustomerAddress Address { get; set; }
}
Okay now lets move on to the <orders> element. I saved this one for last because it seems like you should just be able to do something like this…
public class OrderItem
{
[XmlAttribute(AttributeName = "id")]
public string OrderId { get; set; }
[XmlText]
public string TextValue { get; set; }
}
And simply add a list to the CustomerOrder class representing the different orders. Like this…
[XmlRoot(ElementName = "customerorder")]
public class CustomerOrder
{
[XmlElement(ElementName = "name")]
public CustomerName CustomerName { get; set; }
[XmlElement(ElementName = "contactinfo")]
public CustomerContact ContactInfo { get; set; }
[XmlElement(ElementName = "address")]
public CustomerAddress Address { get; set; }
[XmlElement(ElementName = "order")]
public List<OrderItem> Orders { get; set; }
}
But this wont work, you will only get 1 item in the list and it wont contain any of the values. To deserialize the list properly you will actually need two annotations, one to tell the code what element contains the list and another to tell what the items should go into the list.
[XmlRoot(ElementName = "customerorder")]
public class CustomerOrder
{
[XmlElement(ElementName = "name")]
public CustomerName CustomerName { get; set; }
[XmlElement(ElementName = "contactinfo")]
public CustomerContact ContactInfo { get; set; }
[XmlElement(ElementName = "address")]
public CustomerAddress Address { get; set; }
[XmlArray(ElementName = "orders")]
[XmlArrayItem("order",IsNullable = false)]
public List<OrderItem> Orders { get; set; }
}
Here we see the two annotations needed, XmlArray and XmlArrayItem. We can also see which elements in the xml we are mapping to.
Here is the full code not broken up.
<customerorder>
<name>
<first>John</first>
<middle></middle>
<last>Smith</last>
</name>
<contactinfo email="jsmith@fake.com">
<phone>9998675309</phone>
</contactinfo>
<address isappartment="false">
<street>123 tiger lane</street>
<city>Make Believe</city>
<zip>11111</zip>
</address>
<orders>
<order id="sfswesfy64165">soccor ball</order>
<order id="ssefoofkgh8689">baseball bat</order>
<order id="iiefsefs">soccor net</order>
<order id="owieyfwejif">baseball glove</order>
</orders>
</customerorder>
[XmlRoot(ElementName = "customerorder")]
public class CustomerOrder
{
[XmlElement(ElementName = "name")]
public CustomerName CustomerName { get; set; }
[XmlElement(ElementName = "contactinfo")]
public CustomerContact ContactInfo { get; set; }
[XmlElement(ElementName = "address")]
public CustomerAddress Address { get; set; }
[XmlArray(ElementName = "orders")]
[XmlArrayItem("order",IsNullable = false)]
public List<OrderItem> Orders { get; set; }
}
public class CustomerName
{
[XmlElement(ElementName = "first")]
public string FirstName { get; set; }
[XmlElement(ElementName = "middle")]
public string MiddleName { get; set; }
[XmlElement(ElementName = "last")]
public string LastName { get; set; }
}
public class CustomerContact
{
[XmlAttribute(AttributeName = "email")]
public string EmailAddress { get; set; }
[XmlElement(ElementName = "phone")]
public string PhoneNumber { get; set; }
}
public class CustomerAddress
{
[XmlAttribute(AttributeName = "isappartment")]
public bool IsAppartment { get; set; }
[XmlElement(ElementName = "street")]
public string Street { get; set; }
[XmlElement(ElementName = "city")]
public string City { get; set; }
[XmlElement(ElementName = "zip")]
public string Zip { get; set; }
}
public class OrderItem
{
[XmlAttribute(AttributeName = "id")]
public string OrderId { get; set; }
[XmlText]
public string TextValue { get; set; }
}
Some potential pitfalls on deserialzation that can cause values not to be mapped.
- Case sensitivity, the C# classes property name must match exactly if you are not using annotations.
- C# class properties not being public, properties must be public.
- Inproper nesting, ie child objects not being specified in the correct parent object.
- for this one think about how <order> is a child of <orders> and how we needed two annotations to map this correctly.