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(); } }
bvx
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.
[...] originally used some somewhat dated code I found on Matt Berther’s blog and while it worked, it gave me the nodey version I didn’t care much [...]
I tried to implement this class, but at the line “XmlSerializer xs = new XmlSerializer(typeof(DictionarySerializer))” in the first function I call, Serialization, I get the run time error: “DictionarySerializer is inaccessible due to its protection level.” I’ve tried to set a bunch of things to public. I am calling the function from another class like: DictionarySerializer.Serialize(ht,fs), where hs and fs are a Hashtable and FileStream respectively.
Any ideas why?
Answered my own question. I just had to set the class to public, and excuse my stupidity, but that’s never something I’ve had to do so I didn’t even know it was possible!!
Thanks for providing the code Matt.
I find the IDictionary objects like HashTables and SortedLists to be incredibly useful so being able to serialise them is a must for me.
Thank you Matt for the great idea you have implemented here. It has saved me some time reinventing the wheel. I have slightly improved your DictionarySerializer class so that now it also allows to serialize contained objects as XML providing that they implement IXmlSerializable interface. I thought that it might be worth sharing it with others.
public class DictionarySerializer : IXmlSerializable
{
private IDictionary _dictionary = null;
private DictionarySerializer()
{
_dictionary = new Hashtable();
}
private DictionarySerializer(IDictionary dictionary)
{
_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 void Serialize(IDictionary dictionary, TextWriter textWriter)
{
DictionarySerializer ds = new DictionarySerializer(dictionary);
XmlSerializer xs = new XmlSerializer(typeof(DictionarySerializer));
xs.Serialize(textWriter, ds);
}
public static void Serialize(IDictionary dictionary, XmlWriter xmlWriter)
{
DictionarySerializer ds = new DictionarySerializer(dictionary);
XmlSerializer xs = new XmlSerializer(typeof(DictionarySerializer));
xs.Serialize(xmlWriter, ds);
}
public static IDictionary Deserialize(Stream stream)
{
XmlSerializer xs = new XmlSerializer(typeof(DictionarySerializer));
DictionarySerializer ds = (DictionarySerializer)xs.Deserialize(stream);
return ds._dictionary;
}
public static IDictionary Deserialize(TextReader textReader)
{
XmlSerializer xs = new XmlSerializer(typeof(DictionarySerializer));
DictionarySerializer ds = (DictionarySerializer)xs.Deserialize(textReader);
return ds._dictionary;
}
public static IDictionary Deserialize(XmlReader xmlReader)
{
XmlSerializer xs = new XmlSerializer(typeof(DictionarySerializer));
DictionarySerializer ds = (DictionarySerializer)xs.Deserialize(xmlReader);
return ds._dictionary;
}
#region IXmlSerializable Members
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”);
// Read value
string sType = reader.GetAttribute(”type”);
if (sType == null)
_dictionary.Add(key, reader.ReadElementString(”value”));
else
{
reader.ReadStartElement(”value”);
Type valuetype = Type.GetType(sType);
XmlSerializer xs = new XmlSerializer(valuetype);
_dictionary.Add(key, xs.Deserialize(reader));
reader.ReadEndElement();
reader.ReadEndElement();
}
reader.ReadEndElement();
reader.MoveToContent();
}
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());
// Serialize value
Type valuetype = value.GetType();
if (valuetype.GetInterface(”System.Xml.Serialization.IXmlSerializable”) == null)
writer.WriteElementString(”value”, value.ToString());
else
{
XmlSerializer xs = new XmlSerializer(valuetype);
writer.WriteStartElement(”value”);
writer.WriteAttributeString(”type”, valuetype.FullName);
xs.Serialize(writer, value);
writer.WriteEndElement();
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
#endregion
}
Putting this in your class containing the hashtable works when serializing a hierarchy in which your hashtable is just one of many elemtent.
public DictionarySerializer ParametersXML
{
get { return new DictionarySerializer(parameters); }
set { parameters = (Hashtable) ((DictionarySerializer)value).Dictionary; }
}
In order for this to work you need to make the second constructor public and add a Property like so:
[XmlIgnoreAttribute]
public IDictionary Dictionary
{
get { return dictionary; }
set { dictionary = value; }
}


