XML Serializable Dictionary. Save/Load Dictionary to/from Human Readable XML File.
In this guide we are going to create a dictionary object that can be save/loaded to/from a human readable XML file.
A dictionary with 2 items
(“name1″, “value1″)
(“name2″, “value2″)
will be converted to the following XML file:
<?xml version="1.0" encoding="Windows-1252"?>
<properties>
<add key="name1" value="value1" />
<add key="name2" value="value2" />
</properties>
And vise versa – The XML file file will be converted to a dictionary with 2 items inside.
Click here to download a complete version of this application.
First, let’s create generic SerializableDictionary class:
namespace npCompete.XML
{
[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue>
: Dictionary<TKey, TValue>, IXmlSerializable
{
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
TKey key = (TKey)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
TValue value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
this.Add(key, value);
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key);
writer.WriteEndElement();
writer.WriteStartElement("value");
TValue value = this[key];
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
#endregion
}
}
The class is straight forward, Is is straight forward – we inherited from a generic Dictionary<TKey, TValue> and implemented IXmlSerializable interface.
Now we need a generic XMLFile class to save/load any IXmlSerializable class to/from a file:
namespace npCompete.XML
{
/// <summary>
/// Generic class to save/load an IXmlSerializable class.
/// </summary>
/// <typeparam name="TClass">IXmlSerializable class</typeparam>
public class XMLFile<TClass> where TClass : IXmlSerializable
{
public void Save(TClass classInstance, string fileName)
{
XmlSerializer ser = new XmlSerializer(typeof(TClass));
XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.Default);
try
{
ser.Serialize(writer, classInstance);
}
catch (Exception ex)
{
throw new ApplicationException(
"Exception while saving xml file to "
+ fileName, ex);
}
finally
{
writer.Close();
}
}
public TClass Load(string fileName)
{
TClass className = default(TClass);
XmlSerializer ser = new XmlSerializer(typeof(TClass));
XmlTextReader reader = new XmlTextReader(fileName);
try
{
className = (TClass)ser.Deserialize(reader);
}
catch (Exception ex)
{
throw new ApplicationException(
"Exception while reading xml file "
+ fileName, ex);
}
finally
{
reader.Close();
}
return className;
}
}
}
XMLFile class has 2 self explanatory methods: Save and Load with file name (full file path) and an IXmlSerializable class as parameters.
Let’s merry those 2 classes together and see the results.
1 //SerializableDictionary test. Save dictionary to a file
2 XMLFile<SerializableDictionary<string, string>> XMLFile =
3 new XMLFile<SerializableDictionary<string, string>>();
4
5 SerializableDictionary<string, string> serializableDictionaryToSave =
6 new SerializableDictionary<string, string>();
7
8 serializableDictionaryToSave.Add("name1", "value1");
9 serializableDictionaryToSave.Add("name2", "value2");
10
11
12 string fileName = @"TestSerializableDictionaryXMLFile.xml";
13 XMLFile.Save(serializableDictionaryToSave, fileName);
14
15 //SerializableDictionary test. Load dictionary from a file
16 SerializableDictionary<string, string> loadedSerializableDictionary =
17 new SerializableDictionary<string, string>();
18 loadedSerializableDictionary = XMLFile.Load(fileName);
We created XMLFile (line 2-3), created SerializableDictionary instance with some dictionary items (line 5-8) , save dictionary to a file using XMLFile.Save method (line 13), and load the dictionary back from the file (line 16-18).
Let’s see what the TestSerializableDictionaryXMLFile.xml file look like:
<?xml version="1.0" encoding="Windows-1252"?>
<dictionary>
<item>
<key>
<string>name1</string>
</key>
<value>
<string>value1</string>
</value>
</item>
<item>
<key>
<string>name2</string>
</key>
<value>
<string>value2</string>
</value>
</item>
</dictionary>
Not quite what we would like to see.
We would like to serialize dictionary items into attributes, instead of nodes so the file would be more compact and easier to read.
We want XML look like this:
<?xml version="1.0" encoding="Windows-1252"?>
<properties>
<add key="name1" value="value1" />
<add key="name2" value="value2" />
</properties>
To fix this we need to change our IXmlSerializable implementation for the dictionary class.
I do not want to modify existing SerializableDictionary class, since it is very generic and might be useful in some scenarios.
Let’s create a new, more specialized class called XmlSettingsDictionary.
XmlSettingsDictionary is very similar to SerializableDictionary – we just tweek IXmlSerializable implementation a little bit:
namespace npCompete.XML
{
[XmlRoot("properties")]
public class XmlSettingsDictionary<TKey, TVal>
: Dictionary<TKey, TVal>, IXmlSerializable
{
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer serializer = new XmlSerializer(typeof(XmlKeyValue<TKey, TVal>));
reader.MoveToContent();
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "add")
{
XmlKeyValue<TKey, TVal> entry = (XmlKeyValue<TKey, TVal>)serializer.Deserialize(reader);
this.Add(entry.Key, entry.Value);
}
else
{
reader.Read();
}
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("add");
writer.WriteAttributeString("key", key.ToString());
writer.WriteAttributeString("value", this[key].ToString());
writer.WriteEndElement();
}
}
#endregion
}
/// <summary>
/// Helper struct to control XmlSerialization process.
/// </summary>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
[Serializable()]
[XmlRoot("add")]
public struct XmlKeyValue<K, V>
{
private K key;
private V val;
public XmlKeyValue(K name, V data)
{
this.key = name;
this.val = data;
}
[XmlAttribute("key")]
public K Key
{
get { return key; }
set { key = value; }
}
[XmlAttribute("value")]
public V Value
{
get { return val; }
set { val = value; }
}
}
}
Run code to test this new class:
//SerializableDictionary test. Save dictionary to a file
XMLFile<SerializableDictionary<string, string>> XMLFile =
new XMLFile<SerializableDictionary<string, string>>();
SerializableDictionary<string, string> serializableDictionaryToSave =
new SerializableDictionary<string, string>();
serializableDictionaryToSave.Add("name1", "value1");
serializableDictionaryToSave.Add("name2", "value2");
string fileName = @"TestSerializableDictionaryXMLFile.xml";
XMLFile.Save(serializableDictionaryToSave, fileName);
//SerializableDictionary test. Load dictionary from a file
SerializableDictionary<string, string> loadedSerializableDictionary =
new SerializableDictionary<string, string>();
loadedSerializableDictionary = XMLFile.Load(fileName);
This will produce TestSerializableDictionaryXMLFile.xml file:
<?xml version="1.0" encoding="Windows-1252"?>
<properties>
<add key="name1" value="value1" />
<add key="name2" value="value2" />
</properties>
Exactly, what we wanted. (At this moment, my 3 years old son would say “Good job, papa!”)
Click here to download a complete version of this application.
Hope it helps,