Generic Dictionaries vs. the XmlSerializer

Tweet

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:

  1. First, decorate the Dictionary<TKey, TValue> property with an XmlIgnore attribute. This tells the XmlSerializer to ignore that property, eliminating the reflection error.
  2. 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.
  3. 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="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • pufa

    Isn’t the problem with serializing Dictionaries / Hastables realated to circular refereces? Objects with circular references can’t be xml serialized by the “automagic XmlSerialization”.

    cheers,
    Rui

  • wwb_99

    Not exactly certain what you are hitting at here Rui. The issue appears to me to be that one cannot serialize KeyValuePair, for reasons I admittedly am not certain of. In addition, objects accessible with indexers, eg lists, are serializable without any special voodoo.

  • pwelter34
  • Avi

    Thanks, that what i am searched for.