Serializing a data class to an XML file using XmlSerializer is very useful. However, some of the most useful data classes in .NET are not serializable. Dictionary and Tuple most notably. If you looking for a blog post on how to make a Dictionary that accepts duplicate keys by storing the values values with identical keys in a List, please see this blog post.
The below SerializableDictionary class works by inheriting from IXmlSerializable, which requires you implement the following three methods:
* GetSchema() - Remember, you should always return null for this function.
* ReadXml(XmlReader reader)
* WriteXml(XmlWriter writer)
(Read about IXmlSerializable on MSDN)
Here is the code to serialize a dictionary or serialize a tuple:
namespace XMLSerializableDictionary
{
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
[Serializable]
[XmlRoot("Dictionary")]
public class SerializableDictionary<TKey, TValue>
: Dictionary<TKey, TValue>, IXmlSerializable
{
private const string DefaultTagItem = "Item";
private const string DefaultTagKey = "Key";
private const string DefaultTagValue = "Value";
private static readonly XmlSerializer KeySerializer =
new XmlSerializer(typeof(TKey));
private static readonly XmlSerializer ValueSerializer =
new XmlSerializer(typeof(TValue));
public SerializableDictionary() : base()
{
}
protected SerializableDictionary(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
protected virtual string ItemTagName
{
get { return DefaultTagItem; }
}
protected virtual string KeyTagName
{
get { return DefaultTagKey; }
}
protected virtual string ValueTagName
{
get { return DefaultTagValue; }
}
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
{
return;
}
try
{
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement(this.ItemTagName);
try
{
TKey tKey;
TValue tValue;
reader.ReadStartElement(this.KeyTagName);
try
{
tKey = (TKey)KeySerializer.Deserialize(reader);
}
finally
{
reader.ReadEndElement();
}
reader.ReadStartElement(this.ValueTagName);
try
{
tValue = (TValue)ValueSerializer.Deserialize(reader);
}
finally
{
reader.ReadEndElement();
}
this.Add(tKey, tValue);
}
finally
{
reader.ReadEndElement();
}
reader.MoveToContent();
}
}
finally
{
reader.ReadEndElement();
}
}
public void WriteXml(XmlWriter writer)
{
foreach (KeyValuePair<TKey, TValue> keyValuePair in this)
{
writer.WriteStartElement(this.ItemTagName);
try
{
writer.WriteStartElement(this.KeyTagName);
try
{
KeySerializer.Serialize(writer, keyValuePair.Key);
}
finally
{
writer.WriteEndElement();
}
writer.WriteStartElement(this.ValueTagName);
try
{
ValueSerializer.Serialize(writer, keyValuePair.Value);
}
finally
{
writer.WriteEndElement();
}
}
finally
{
writer.WriteEndElement();
}
}
}
}
}
The idea behind the serializable tuple is we just make our own Tuple that stores the items by declaring the properties to represent them with their generic T type. If you are not used to working with generics, this can be a little strange. T1, T2 and T3 are just placeholders for the type that is to be determined by the calling function, or the function above that if the calling function uses generics too.
And a serializable tuple:
public class SerializableTuple<T1,T2,T3>
{
public T1 Item1 { get; set; }
public T2 Item2 { get; set; }
public T3 Item3 { get; set; }
public static implicit operator Tuple<T1,T2,T3>(SerializableTuple<T1,T2,T3> st)
{
return Tuple.Create(st.Item1,st.Item2,st.Item3);
}
public static implicit operator SerializableTuple<T1,T2,T3>(Tuple<T1,T2,T3> t)
{
return new SerializableTuple<T1,T2,T3>() {
Item1 = t.Item1,
Item2 = t.Item2,
Item3 = t.Item3
};
}
public SerializableTuple()
{
}
}
And finally, a generic object serializer and deserializer:
public static class XML
{
public static class Serialize
{
public static void Object(string Filename, object obj)
{
using (StreamWriter streamWriter = new StreamWriter(Filename))
{
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
xmlSerializer.Serialize(streamWriter, obj);
}
}
}
public static class DeSerialize
{
public static string Generic<T>(T data)
{
if (data == null)
return string.Empty;
string content = string.Empty;
using (MemoryStream memoryStream = new MemoryStream())
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
serializer.Serialize(memoryStream, data);
memoryStream.Seek(0, SeekOrigin.Begin);
using (StreamReader reader = new StreamReader(memoryStream))
{
content = reader.ReadToEnd();
}
}
return content;
}
public static object Object(string Filename, Type type)
{
object result = null;
using (TextReader reader = new StringReader(Filename))
{
XmlSerializer serializer = new XmlSerializer(type);
result = serializer.Deserialize(reader);
}
return result;
}
}
}
And perhaps after you serialize your data to an XML file, you would like to generate a schema XML file from it:
void XmlToSchema(string FileName)
{
XmlReader xmlReader = XmlReader.Create(FileName);
XmlSchemaSet schemaSet = new XmlSchemaSet();
XmlSchemaInference schemaInfer = new XmlSchemaInference();
schemaSet = schemaInfer.InferSchema(xmlReader);
string outFilename = Path.ChangeExtension(FileName,".xsd");
using(Stream streamOut = new FileStream(outFilename,FileMode.Create) )
{
TextWriter textWriter = new StreamWriter(streamOut);
foreach (XmlSchema s in schemaSet.Schemas())
{
s.Write(textWriter );
}
textWriter .Close();
}
}