This blog is in continuation of my other blog. In the first blog, I explained how to use XML attribute overrides to reduce the number of attributes required in source code, and how to centralize some common functionality in a single location (the blog focused on how to serialize to XML using camel casing as default for tag names). I will be referring to code from that first blog, so I do recommend you to read that one first.
That said, let’s get into that topic: REST Services many times return a collection, a list of objects. When the format is JSON, the returned text will look something like the following.
- [ { prop1 = “x”, prop2 = “y’} , { prop1 = “a”, prop2 = “b”}, …..]
When the response is XML, the code might look like this.
- <allCustomers>
- <myCustomer>
- <prop1>x</prop1>
- <prop2>y</prop2>
- </myCustomer>
- <myCustomer>
- etc
- </myCustomer>
- </allCustomers>
In order to deserialize this content, people see the need to create a helping class, like this one.
- [XmlRoot(ElementName = "allCustomer")]
- public class ListOfCustomers : List<MyCustomer>
- {
- }
So, a real project ends up having many classes like this one: Empty, and repetitive. Well, when something is repetitive, there must be a better way. And when something is empty.... yikes! Awful!
Here is the tricky part: Marking class MyCustomer with an XmlRoot attribute won’t do the trick. The root is of type List<MyCustomer>, so an XmlRoot attribute tied to MyCustomer does nothing.
There is another problem to solve - Whether “MyCustomer” has an XmlRoot attribute or not. There has to be a way to tell the deserializer the tag name to use when finding a list. It could be programmed in the attribute override code to be setting all such tags to be something like listOfxxxxx where xxxxx is the name of the generic element. But this solution would be restrictive. This will definitely not help in many real life scenarios. Another way is to create a new attribute
- [XmlObjectWrapper(ElementName = "allCustomers")]
This attribute will be used when processing the overrides. It will tell the code that generates the overrides: “Hey, if you ever need to deserialize a list, or array of these type of object, the surrounding tag name should be allCustomers”. So you do need to add this new attribute to the classes which you know will become members of a list to be deserialized.
The definition of the new attribute is quite simple:
- namespace MyProject
- {
- using System;
- public class XmlObjectWrapperAttribute : Attribute
- {
- public string ElementName { get; set; }
- }
- }
Here is the definition of class MyCustomer now including this attribute:
- [XmlObjectWrapper(ElementName = "allCustomers")]
- public class MyCustomer
- {
- public string NameAndLastName { get; set; }
- public int Age { get; set; }
- public Double Height { get; set; }
- public Gender Gender { get; set; }
- public List<Book> ReadingPile { get; set; }
- public Boolean Active { get; set; }
- [XmlElement(ElementName = "WEEKLY_MONEY")]
- public Decimal Allowance { get; set; }
- public MyCustomer ReferredBy { get; set; }
- }
Here, I repeat the “CreateAttributeOverrides” method, which I published in my previous blog. This one is the improved version which handles deserialization of objects into Lists of something. I have highlighted the relevant code.
- public static XmlAttributeOverrides CreateAttributeOverrides(Type objectType)
- {
- Func<Type, bool> IsList = t => t.IsGenericType &&
- t.GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>));
- HashSet<Type> workTypes = GetTypesToOverride(objectType);
- XmlAttributeOverrides theBag = new XmlAttributeOverrides();
- var classNames = new HashSet<string>();
- classNames.Add(objectType.FullName);
- while (workTypes.Count > 0)
- {
- Type singleType = workTypes.First();
- workTypes.Remove(singleType);
- if (HasXmlAttributes(singleType))
- continue;
- var wrapperAttr = singleType.GetCustomAttribute<XmlObjectWrapperAttribute>(true/*inherit*/);
- if (wrapperAttr != null)
- {
- string wrapperTagName = wrapperAttr.ElementName;
- Type listType = typeof(List<>);
- Type listOfTType = listType.MakeGenericType(singleType);
- string elementTagName = singleType.Name.ToCamel();
- theBag.Add(
- singleType,
- new XmlAttributes()
- {
- XmlType = new XmlTypeAttribute(elementTagName),
- XmlRoot = new XmlRootAttribute("OUTEROBJECT")
- });
- theBag.Add(
- listOfTType,
- new XmlAttributes() { XmlRoot = new XmlRootAttribute(wrapperTagName) });
- classNames.Add(singleType.FullName);
- workTypes.Remove(singleType);
- }
- else if (singleType == objectType)
- {
- theBag.Add(objectType, new XmlAttributes() {XmlRoot = new XmlRootAttribute("OUTEROBJECT") });
- }
- PropertyInfo[] allPropsInfo = singleType.GetProperties();
- foreach (PropertyInfo propInfo in allPropsInfo)
- {
- HashSet<Type> overridableTypes = GetTypesToOverride(propInfo.PropertyType);
- bool propHasXmlAttributes = HasXmlAttributes(propInfo);
- if (propHasXmlAttributes)
- overridableTypes.Remove(propInfo.PropertyType);
- List<String> overridableNames = overridableTypes.Select(t => t.FullName).ToList();
- overridableTypes.RemoveWhere(t => classNames.Contains(t.FullName));
- classNames.UnionWith(overridableTypes.Select(t => t.FullName));
- workTypes.UnionWith(overridableTypes);
- if (propHasXmlAttributes)
- continue;
- string camelName = propInfo.Name.ToCamel();
- var propOverrides = new XmlAttributes();
- Type propType = propInfo.PropertyType;
- if (propType.IsArray || IsList(propType))
- {
- string pascalName = propInfo.Name.ToPascal();
- propOverrides.XmlArray = new XmlArrayAttribute("COLLECTION" + pascalName);
- propOverrides.XmlArrayItems.Add(new XmlArrayItemAttribute(camelName));
- }
- else
- {
- propOverrides.XmlElements.Add(new XmlElementAttribute(camelName));
- }
- theBag.Add(singleType, propInfo.Name, propOverrides);
- }
- }
- return theBag;
- }
- public static T Deserialize<T>(string s)
- {
- T returnValue = default(T);
- Type returnType = typeof(T);
- XmlAttributeOverrides xmlOverrides = CreateAttributeOverrides(returnType);
- XmlSerializer serializer = new XmlSerializer(returnType, xmlOverrides);
- using (TextReader reader = new StringReader(s))
- {
- returnValue = (T)serializer.Deserialize(reader);
- }
- return returnValue;
- }
Next, the test code which exercises all the above code. Despite being coded as a test, it's not really a test. It just helps illustrate the execution of the above code.
- [Test]
- public void TestDeserializeIntoList()
- {
- string serializedListOfCustomers = File.ReadAllText("c:\\temp\\serialized.txt", Encoding.UTF8);
- List<MyCustomer> custList = XmlCamelOverrides.Deserialize<List<MyCustomer>>(serializedListOfCustomers);
- Console.WriteLine("custList has {0} elements.", custList.Count);
- }
Finally, a text file with serialized code to deserialize. Note that all the tags use camel casing, just the same as in my previous blog.
- <?xml version="1.0" encoding="utf-8"?>
- <allCustomers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
- <myCustomer>
- <nameAndLastName>Lee Leevan</nameAndLastName>
- <age>35</age>
- <height>6.01</height>
- <gender>Male</gender>
- <COLLECTIONReadingPile>
- <readingPile>
- <title>C# for Dummies</title>
- <color>Yellow</color>
- </readingPile>
- <readingPile>
- <title>The Black Box</title>
- <color>Orange</color>
- </readingPile>
- <readingPile>
- <title>The Red Sea</title>
- <color>White</color>
- </readingPile>
- </COLLECTIONReadingPile>
- <active>true</active>
- <WEEKLY_MONEY>21.61</WEEKLY_MONEY>
- <referredBy>
- <nameAndLastName>Leann Lunn</nameAndLastName>
- <age>32</age>
- <height>6.01</height>
- <gender>Female</gender>
- <COLLECTIONReadingPile />
- <active>false</active>
- <WEEKLY_MONEY>21.61</WEEKLY_MONEY>
- </referredBy>
- </myCustomer>
- </allCustomers>
Run the code. The XML will deserialize into a List<MyCustomer>, with 1 element.

Time to remove all the empty classes and make code simpler.
While XML attribute overrides are not easy to learn and use, they are powerful, and as I showed here, they open the doors to do things which reduce the amount of code you need to write. Less code means your code is cleaner, easier to understand, and more maintainable. Less code duplication means that if there is a bug, it will be fixed in one location, not hundreds of places.
Join the conversation! Your thoughts help the community grow.