Serializing an IDictionary object
As of late, I really try to stay away from the XmlSerializer object, as I have found its limitations to be quite hindering in a lot of things I try to do.
One of the major drawbacks to this class is the serialization of an IDictionary based object. Every time that I need to do this, I end up looking towards Aaron Skonnard’s column in the MSDN Magazine.
He details a fairly simple implementation that allows you to serialize an IDictionary by wrapping it in a custom object that implements IXmlSerializable.
I took his code and added a few convenience methods and present them here (mostly for my reference later). Enjoy!
class DictionarySerializer : IXmlSerializable { private IDictionary dictionary; public DictionarySerializer() { this.dictionary = new Hashtable(); } private DictionarySerializer(IDictionary dictionary) { this.dictionary = dictionary; } public static void Serialize(IDictionary dictionary, Stream stream) { DictionarySerializer ds = new DictionarySerializer(dictionary); XmlSerializer xs = new XmlSerializer(typeof(DictionarySerializer)); xs.Serialize(stream, ds); } public static IDictionary Deserialize(Stream stream) { XmlSerializer xs = new XmlSerializer(typeof(DictionarySerializer)); DictionarySerializer ds = (DictionarySerializer)xs.Deserialize(stream); return ds.dictionary; } XmlSchema IXmlSerializable.GetSchema() { return null; } void IXmlSerializable.ReadXml(XmlReader reader) { reader.Read(); reader.ReadStartElement("dictionary"); while (reader.NodeType != XmlNodeType.EndElement) { reader.ReadStartElement("item"); string key = reader.ReadElementString("key"); string value = reader.ReadElementString("value"); reader.ReadEndElement(); reader.MoveToContent(); dictionary.Add(key, value); } reader.ReadEndElement(); } void IXmlSerializable.WriteXml(XmlWriter writer) { writer.WriteStartElement("dictionary"); foreach (object key in dictionary.Keys) { object value = dictionary[key]; writer.WriteStartElement("item"); writer.WriteElementString("key", key.ToString()); writer.WriteElementString("value", value.ToString()); writer.WriteEndElement(); } writer.WriteEndElement(); } }
Dredging up an old post here, but just in case anybody stumbles across it through google as I did:
this code, like Aaron’s code that it comes from, is missing a
reader.ReadEndElement()
in the ReadXml method (the one that closes the dictionary tag). This can stop it from working in situations where the serialized dictionary is part of a larger object graph.
You’re absolutely correct, Jony. Thanks for the heads-up. I’ve updated the code listing.
For those who are struggling to find the correct namespaces:
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Collections;
using System.IO;
are required.
Dave.
Greetings,
Thank you for this wonderful article, it helped me so much. I permit me ask you about one problem that I’ve meet. I use .NET 1.1, your class works correctly in web methods, but I have a problem to pass Serialized HashTable from web method to some asp .net application, using web references. When VS environment generate proxy class, it define your DictionarySerializer like a DataSet. For example for next web method:
[Webmethod]
public DictionarySerializer Test()
{
………
return new DictionarySerializer(MyHashTable);
}
VS will generate something like that in a proxy class:
public System.Data.DataSet Test() {
object[] results = this.Invoke(”Test”, new object[0]);
return ((System.Data.DataSet)(results[0]));
}
Without a schema specification the system define serializeble object like a DataSet. What I need to correct this problem, can you help me to do that? Thank you in advance.
Best regards
Victor Bartel
hi, i have this class, which has all the collection properties and methods. in it i have a property, Total, which returns the number of elements.
public class PostcodeCollection : CollectionBase
{
// .... other collection's stuff
[XmlAttribute( "Total" )]
public int Total
{
get { return List.Count; }
set
{}
}
}
this collection is a property in another class:
[XmlRoot("PostCodes" )]
public class PostcodeReport
{
// ...other properties
[XmlArray("Updated")]
[XmlArrayItem(typeof(PostCodeLine), ElementName="PostCode")]
public PostcodeCollection Updated
{
get { return this.updated; }
}
}
i serialize the collection, and all is ok, but i want to add the “Total” property to the xml:
This is how the xml must look like:
i don’t know how to apply your example, any thoughts?
thanks,
Daniel
this is the xml, i don’t know why it wasn’t saved:
This is how the xml must look like:
The serialization loses what I would consider one of the main advantages of the Hashtable and that is the type of the value.
A DateTime value is deserialized as a string and not a DateTime.
Everything being serialized as a string, and the non-recursive nature of the serialization make this kind of useless. I’ve ended up writing a couple of helper methods that will serialize and deserialize nested IDictionary objects too.
public static StringBuilder Serialize(StringBuilder sb, object obj)
{
if (typeof(IDictionary).IsAssignableFrom(obj.GetType()))
{
sb.Append("");
foreach (object key in ((IDictionary)obj).Keys)
{
sb.Append("");
Serialize(sb, ((IDictionary)obj)[key]);
sb.Append("");
}
sb.Append("");
}
else
{
StringWriter sw = new StringWriter(sb);
XmlSerializer xs = new XmlSerializer(obj.GetType());
sb.Append("");
//need a bodge in here to deal with unnamed datatables
xs.Serialize(sw, obj);
sb.Append("");
}
return sb.Replace("", "");
}
public static object Deserialize(string s)
{
Object obj = new Object();
XmlDocument xml = new XmlDocument();
xml.LoadXml(s);
XmlNode node = xml.ChildNodes[0];
if (node.Name.Contains("IDictionary_"))
{
obj = Activator.CreateInstance(Type.GetType(XmlConvert.DecodeName(node.Name.Replace("IDictionary_", ""))));
foreach (XmlNode child in node.ChildNodes)
((IDictionary)obj).Add(child.Name.Replace("Key_", ""), Deserialize(child.InnerXml));
}
else if (node.Name.Contains("Type_"))
{
StringReader sr = new StringReader("" + node.InnerXml);
XmlSerializer xs = new XmlSerializer(Type.GetType(XmlConvert.DecodeName(node.Name.Replace("Type_", ""))));
obj = xs.Deserialize(sr);
}
return obj;
}
Use it like this:
Hashtable ht1 = new Hashtable();
ht1["1"] = 1;
ht1["2"] = 2;
Hashtable ht2 = new Hashtable();
ht2["a"] = "a";
ht2["b"] = "b";
ht1["3"] = ht2;
Serialize(new StringBuilder(), ht1);
Hmm, that comment has mangled some of the XML tags that were in the code, the method won’t work as it is shown on this page
This works very well,
gooood work ,
data gets serialized successfully,
but does not gets deserialize !!!
my problem is smwht like this, I have Four hashtable that stores the key n value pair,
on Form I have 4 combobox n 4 respective textbox ,
I wnt to populate the Key in combobx n Its RESPECTIVE value in textbox
on the selectedindex event of the combx, I where I write a code
I gets an error, tht there is a problem with XML document !!!
this XMl doc is well-formed, No-bugs thr..
this means tht the data is nt deserializing …
If you get this, plz help me out m stuck in this prblm since last 2 days :(
I think the function ‘ReadXml’ needs an extra function call to ‘reader.ReadEndElement()’ to fully close out the working node. This should allow it to work within larger object graphs as well.

bvx