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!
In this case, there is a solution. All these empty classes can be avoided with the use of Attribute overrides. Basically, the deserializer needs to know that a tag is associated with a type. And the emphasis here is in the word type. It does not need to be a class. It needs to be a type. And List<MyCustomer> is a type. So, that’s a step forward. The other piece of the puzzle is telling the deserializer that whenever it sees tag “allCustomers”, it must deserialize into this type.
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.
If the object of this class were a property, then it would be marked, for example, with XmlElement attribute. But it's not an element. Here, MyCustomer plays more the role of XmlArrayItem than anything else. But a class cannot be marked with an XmlArrayItem attribute. This is reserved for class members. So, then, how is it done? The right way is to mark the class with attribute XmlType. Adding this tells the deserializer that this class exists, and it’s a type to be used by other types. Without this definition in the "bag of overrides", the list will come out empty when deserialized.
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);
- 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;
- }
And now, the final few pieces to demonstrate the code is working. First, some code to deserialize XML strings to objects:
- 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.
Conclusion
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.