Generic Dictionaries vs. the XmlSerializer
One of the neatest new features of .NET 2.0 is the concept of Generics. And one of the most common uses of this feature is in the new GenericCollections. Anyone who spent quality time extending CollectionBase and DictionaryBase in 1.1 (or just cheating using CodeSmith’s collection gen) will understand how useful these collections are.
Another of the very handy features of .NET is the built-in, nearly automagic XmlSerialization. Presuming one follows a few simple rules—principally having a blank, default constructor and making sure all properties implement Get and Set accessors—one can instantaneously serialze any object to Xml. This tactic, unfortunately, falls apart when one attempts to make use of the new Generic dictionary as a member of an object one wishes to XmlSerialize. Rather than a bit of xml, you get a nice little exception stating “There was an error reflecting type ‘your type name here’”.
There are a number of ways to workaround this issue. Most involve implementing IXmlSerializable, which tends to lead to writing a lot of repetitive code. And these techniques are a good option if you are writing to and reading from a very specific schema. However, many times, one just needs to get something serialized in a quick and dirty, but working manner for use with, say, some internal Web Service or some kind of temporary storage.
In that case, one can use the following technique:
- First, decorate the Dictionary<TKey, TValue> property with an XmlIgnore attribute. This tells the XmlSerializer to ignore that property, eliminating the reflection error.
- Create a new public property that takes and returns an array of DictionaryEntry objects. I tend to name these specially (_x_DictionaryName) so that it is clear one should not generally use this property.
- Serialize away.
In case the above is a bit obtuse, see below for an example:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
namespace SPBlog.DictionarySerialization
{
[XmlRoot("StuffContainer")]
public class Serializable
{
public Serializable() { }
private Dictionary<string, int> stuff=new Dictionary<string,int>();
/// <summary>
/// Public stuff dictionary.
/// </summary>
/// <remarks>
/// Note the XmlIgnore attribute.
/// </remarks>
[XmlIgnore()]
public Dictionary<string, int> Stuff
{
set { stuff = value; }
get { return stuff; }
}
/// <summary>
/// Property created expressly for the XmlSerializer
/// </summary>
/// <remarks>
/// Note the XML Serialiazation attributes; they control what elements are named when this object is serialized.
/// </remarks>
[XmlArray("Stuff")]
[XmlArrayItem("StuffLine", Type=typeof(DictionaryEntry))]
public DictionaryEntry[] _x_Stuff
{
get
{
//Make an array of DictionaryEntries to return
DictionaryEntry[] ret=new DictionaryEntry[Stuff.Count];
int i=0;
DictionaryEntry de;
//Iterate through Stuff to load items into the array.
foreach (KeyValuePair<string, int> stuffLine in Stuff)
{
de = new DictionaryEntry();
de.Key = stuffLine.Key;
de.Value = stuffLine.Value;
ret[i]=de;
i++;
}
return ret;
}
set
{
Stuff.Clear();
for (int i=0; i<value.Length; i++)
{
Stuff.Add((string)value[i].Key, (int)value[i].Value);
}
}
}
}
}
Which creates the following Xml:
<?xml version="1.0" encoding="utf-8"?>
<StuffContainer xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema">
<Stuff>
<StuffLine>
<Key xsi:type="xsd:string">One</Key>
<Value xsi:type="xsd:int">1</Value>
</StuffLine>
<StuffLine>
<Key xsi:type="xsd:string">Two</Key>
<Value xsi:type="xsd:int">2</Value>
</StuffLine>
<StuffLine>
<Key xsi:type="xsd:string">Three</Key>
<Value xsi:type="xsd:int">3</Value>
</StuffLine>
<StuffLine>
<Key xsi:type="xsd:string">Four</Key>
<Value xsi:type="xsd:int">4</Value>
</StuffLine>
</Stuff>
</StuffContainer>
Now, as you can see above, the DictionaryEntry deserializes in an interesting way—it adds a type attribute to the Key and Value nodes. One could easily modify this technique to use a custom object that serializes to specific types if one needs a bit more finite control of the output.