Why isn't there an XML-serializable dictionary in .NET?

.NetDictionaryXml SerializationAppsettings

.Net Problem Overview


I need an XML-serializable dictionary. Actually, I now have two quite different programs that need one. I was rather surprised to see that .NET doesn't have one.

Can someone enlighten me, given how dependent various .NET features are on XML serialization, why there isn't an XML-serializable dictionary?

.Net Solutions


Solution 1 - .Net

I know this has been answered before, but since I have a very concise way (code) for doing IDictionary serialization with the DataContractSerializer class (used by WCF, but could and should be used anywhere) I couldn't resist contributing it here:

public static class SerializationExtensions
{
    public static string Serialize<T>(this T obj)
    {
        var serializer = new DataContractSerializer(obj.GetType());
        using (var writer = new StringWriter())
        using (var stm = new XmlTextWriter(writer))
        {
            serializer.WriteObject(stm, obj);
            return writer.ToString();
        }
    }
    public static T Deserialize<T>(this string serialized)
    {
        var serializer = new DataContractSerializer(typeof(T));
        using (var reader = new StringReader(serialized))
        using (var stm = new XmlTextReader(reader))
        {
            return (T)serializer.ReadObject(stm);
        }
    }
}

This works perfectly in .NET 4 and should also work in .NET 3.5, although I didn't test it yet.

UPDATE: It doesn't work in .NET Compact Framework (not even NETCF 3.7 for Windows Phone 7) as the DataContractSerializer is not supported!

I did the streaming to string because it was more convenient to me, although I could have introduced a lower-level serialization to Stream and then used it to serialize to strings, but I tend to generalize only when needed (just like premature optimization is evil, so it is premature generalization...)

Usage is very simple:

// dictionary to serialize to string
Dictionary<string, object> myDict = new Dictionary<string, object>();
// add items to the dictionary...
myDict.Add(...);
// serialization is straight-forward
string serialized = myDict.Serialize();
...
// deserialization is just as simple
Dictionary<string, object> myDictCopy = 
    serialized.Deserialize<Dictionary<string,object>>();

myDictCopy will be a verbatim copy of myDict.

You'll also notice that the generic methods provided will be able to serialize any type (to the best of my knowledge) since it is not limited to IDictionary interfaces, it can be really any generic type T.

Hope it helps someone out there!

Solution 2 - .Net

The thing about XML Serialization is that it's not just about creating a stream of bytes. It's also about creating an XML Schema that this stream of bytes would validate against. There's no good way in XML Schema to represent a dictionary. The best you could do is to show that there's a unique key.

You can always create your own wrapper, for instance One Way to Serialize Dictionaries.

Solution 3 - .Net

They added one in .NET 3.0. If you can, add a reference to System.Runtime.Serialization and look for System.Xml.XmlDictionary, System.Xml.XmlDictionaryReader, and System.Xml.XmlDictionaryWriter.

I would agree that it is not in a particularly discoverable place.

Solution 4 - .Net

Create one of your own :-), the readonly feature is bonus but if you need a key other than a string then the class needs some modifications...

namespace MyNameSpace
{
	[XmlRoot("SerializableDictionary")]
	public class SerializableDictionary : Dictionary<String, Object>, IXmlSerializable
	{
		internal Boolean _ReadOnly = false;
		public Boolean ReadOnly
		{
			get
			{
				return this._ReadOnly;
			}

			set
			{
				this.CheckReadOnly();
				this._ReadOnly = value;
			}
		}

		public new Object this[String key]
		{
			get
			{
				Object value;

				return this.TryGetValue(key, out value) ? value : null;
			}

			set
			{
				this.CheckReadOnly();

				if(value != null)
				{
					base[key] = value;
				}
				else
				{
					this.Remove(key);
				}				
			}
		}
		
		internal void CheckReadOnly()
		{
			if(this._ReadOnly)
			{
				throw new Exception("Collection is read only");
			}
		}

		public new void Clear()
		{
			this.CheckReadOnly();

			base.Clear();
		}

		public new void Add(String key, Object value)
		{
			this.CheckReadOnly();

			base.Add(key, value);
		}

		public new void Remove(String key)
		{
			this.CheckReadOnly();

			base.Remove(key);
		}

		public XmlSchema GetSchema()
		{
			return null;
		}

		public void ReadXml(XmlReader reader)
		{
			Boolean wasEmpty = reader.IsEmptyElement;

			reader.Read();

			if(wasEmpty)
			{
				return;
			}

			while(reader.NodeType != XmlNodeType.EndElement)
			{
				if(reader.Name == "Item")
				{
					String key = reader.GetAttribute("Key");
					Type type = Type.GetType(reader.GetAttribute("TypeName"));

					reader.Read();
					if(type != null)
					{
						this.Add(key, new XmlSerializer(type).Deserialize(reader));
					}
					else
					{
						reader.Skip();
					}
					reader.ReadEndElement();

					reader.MoveToContent();
				}
                else
                {
                    reader.ReadToFollowing("Item");
                }
			    
			reader.ReadEndElement();
		}

		public void WriteXml(XmlWriter writer)
		{
			foreach(KeyValuePair<String, Object> item in this)
			{
				writer.WriteStartElement("Item");
				writer.WriteAttributeString("Key", item.Key);
				writer.WriteAttributeString("TypeName", item.Value.GetType().AssemblyQualifiedName);

				new XmlSerializer(item.Value.GetType()).Serialize(writer, item.Value);

				writer.WriteEndElement();
			}
		}

	}
}

Solution 5 - .Net

Use the DataContractSerializer! See the sample below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Xml;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.Value = 1;

            B b = new B();
            b.Value = "SomeValue";

            Dictionary<A, B> d = new Dictionary<A,B>();
            d.Add(a, b);
            DataContractSerializer dcs = new DataContractSerializer(typeof(Dictionary<A, B>));
            StringBuilder sb = new StringBuilder();
            using (XmlWriter xw = XmlWriter.Create(sb))
            {
                dcs.WriteObject(xw, d);
            }
            string xml = sb.ToString();
        }
    }

    public class A
    {
        public int Value
        {
            get;
            set;
        }
    }

    public class B
    {
        public string Value
        {
            get;
            set;
        }
    }
}

The above code produces the following xml:

<?xml version="1.0" encoding="utf-16"?>
<ArrayOfKeyValueOfABHtQdUIlS xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
    <KeyValueOfABHtQdUIlS>
        <Key xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
            <d3p1:Value>1</d3p1:Value>
        </Key>
        <Value xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
            <d3p1:Value>SomeValue</d3p1:Value>
        </Value>
    </KeyValueOfABHtQdUIlS>
</ArrayOfKeyValueOfABHtQdUIlS>

Solution 6 - .Net

A generic helper to quickly add IXmlSerializable to any (existing) Dictionary without using inheritance:

using System.Xml;
using System.Xml.Serialization;
using System.Collections.Generic;

namespace GameSpace {

	public class XmlSerializerForDictionary {

		public struct Pair<TKey,TValue> {

			public TKey Key;
			public TValue Value;

			public Pair(KeyValuePair<TKey,TValue> pair) {
				Key = pair.Key;
				Value = pair.Value;
			}//method

		}//struct

		public static void WriteXml<TKey,TValue>(XmlWriter writer, IDictionary<TKey,TValue> dict) {
			
			var list = new List<Pair<TKey,TValue>>(dict.Count);

			foreach (var pair in dict) {
				list.Add(new Pair<TKey,TValue>(pair));
			}//foreach

			var serializer = new XmlSerializer(list.GetType());
			serializer.Serialize(writer, list);

		}//method

		public static void ReadXml<TKey, TValue>(XmlReader reader, IDictionary<TKey, TValue> dict) {

			reader.Read();
			
			var serializer = new XmlSerializer(typeof(List<Pair<TKey,TValue>>));
			var list = (List<Pair<TKey,TValue>>)serializer.Deserialize(reader);

			foreach (var pair in list) {
				dict.Add(pair.Key, pair.Value);
			}//foreach

			reader.Read();
			
		}//method

	}//class

}//namespace

And a convenient serializable generic dictionary:

using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Collections.Generic;

namespace GameSpace {
	
	public class SerializableDictionary<TKey,TValue> : Dictionary<TKey,TValue>, IXmlSerializable {

		public virtual void WriteXml(XmlWriter writer) {
			XmlSerializerForDictionary.WriteXml(writer, this);
		}//method

		public virtual void ReadXml(XmlReader reader) {
			XmlSerializerForDictionary.ReadXml(reader, this);
		}//method

		public virtual XmlSchema GetSchema() {
			return null;
		}//method

	}//class

}//namespace

Solution 7 - .Net

This is my implementation.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;

namespace Rubik.Staging
{    
    [XmlSchemaProvider("GetInternalSchema")]
    public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
    {
        #region IXmlSerializable Members

        private const string ns = "http://www.rubik.com.tr/staging";
        
        public static XmlQualifiedName GetInternalSchema(XmlSchemaSet xs)
        {
            bool keyIsSimple = (typeof(TKey).IsPrimitive || typeof(TKey) == typeof(string));
            bool valueIsSimple = (typeof(TValue).IsPrimitive || typeof(TValue) == typeof(string));
           
            XmlSchemas schemas = new XmlSchemas();
                    
            XmlReflectionImporter importer = new XmlReflectionImporter(ns);
            importer.IncludeType(typeof(TKey));            
            importer.IncludeType(typeof(TValue));            
            
            XmlTypeMapping keyMapping = importer.ImportTypeMapping(typeof(TKey));            
            XmlTypeMapping valueMapping = importer.ImportTypeMapping(typeof(TValue));          

            XmlSchemaExporter exporter = new XmlSchemaExporter(schemas); 
           
            if(!keyIsSimple)
                exporter.ExportTypeMapping(keyMapping);
            if(!valueIsSimple)
                exporter.ExportTypeMapping(valueMapping);

            XmlSchema schema = (schemas.Count == 0 ? new XmlSchema() : schemas[0]);
            
            schema.TargetNamespace = ns;          
            XmlSchemaComplexType type = new XmlSchemaComplexType();
            type.Name = "DictionaryOf" + keyMapping.XsdTypeName + "And" + valueMapping.XsdTypeName;
            XmlSchemaSequence sequence = new XmlSchemaSequence();
            XmlSchemaElement item = new XmlSchemaElement();
            item.Name = "Item";

            XmlSchemaComplexType itemType = new XmlSchemaComplexType();            
            XmlSchemaSequence itemSequence = new XmlSchemaSequence();
           
            XmlSchemaElement keyElement = new XmlSchemaElement();

            keyElement.Name = "Key";
            keyElement.MaxOccurs = 1;
            keyElement.MinOccurs = 1;

            XmlSchemaComplexType keyType = new XmlSchemaComplexType();
            XmlSchemaSequence keySequence = new XmlSchemaSequence();
            XmlSchemaElement keyValueElement = new XmlSchemaElement();
            keyValueElement.Name = keyMapping.ElementName;
            keyValueElement.SchemaTypeName = new XmlQualifiedName(keyMapping.XsdTypeName, keyMapping.XsdTypeNamespace);
            keyValueElement.MinOccurs = 1;
            keyValueElement.MaxOccurs = 1;
            keySequence.Items.Add(keyValueElement);
            keyType.Particle = keySequence;
            keyElement.SchemaType = keyType;
            itemSequence.Items.Add(keyElement);


            XmlSchemaElement valueElement = new XmlSchemaElement();

            valueElement.Name = "Value";
            valueElement.MaxOccurs = 1;
            valueElement.MinOccurs = 1;

            XmlSchemaComplexType valueType = new XmlSchemaComplexType();
            XmlSchemaSequence valueSequence = new XmlSchemaSequence();
            XmlSchemaElement valueValueElement = new XmlSchemaElement();
            valueValueElement.Name = valueMapping.ElementName;
            valueValueElement.SchemaTypeName = new XmlQualifiedName(valueMapping.XsdTypeName, valueMapping.XsdTypeNamespace);
            valueValueElement.MinOccurs = 1;
            valueValueElement.MaxOccurs = 1;
            valueSequence.Items.Add(valueValueElement);
            valueType.Particle = valueSequence;
            valueElement.SchemaType = valueType;
            itemSequence.Items.Add(valueElement);
            itemType.Particle = itemSequence;
            item.SchemaType = itemType;            
            sequence.Items.Add(item);
            type.Particle = sequence;
            schema.Items.Add(type);

            xs.XmlResolver = new XmlUrlResolver();
            xs.Add(schema);

            return new XmlQualifiedName(type.Name, ns);
        }





        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();

            if (wasEmpty)
                return;

            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

        #region IXmlSerializable Members

        public XmlSchema GetSchema()
        {
            return null;
        }

        #endregion
    }

}

Solution 8 - .Net

I know this has been done to death now, but here is my contribution. I took the good bits from the solutions from @Loudenvier and @Jack and wrote my own serialisable (sorry, I'm British) dictionary class.

public class SerialisableDictionary<T1, T2> : Dictionary<T1, T2>, IXmlSerializable
{
    private static DataContractSerializer serializer =
        new DataContractSerializer(typeof(Dictionary<T1, T2>));

    public void WriteXml(XmlWriter writer)
    {
        serializer.WriteObject(writer, this);
    }

    public void ReadXml(XmlReader reader)
    {
        Dictionary<T1, T2> deserialised =
            (Dictionary<T1, T2>)serializer.ReadObject(reader);
        
        foreach(KeyValuePair<T1, T2> kvp in deserialised)
        {
            Add(kvp.Key, kvp.Value);
        }
    }

    public XmlSchema GetSchema()
    {
        return null;
    }
}

I like this approach because you won't have to explicitly serialise or deserialise anything, just pump the whole class hierarchy through an XmlSerializer and you're done.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionserialhobbyistView Question on Stackoverflow
Solution 1 - .NetLoudenvierView Answer on Stackoverflow
Solution 2 - .NetJohn SaundersView Answer on Stackoverflow
Solution 3 - .NetJoe ChungView Answer on Stackoverflow
Solution 4 - .NetbangView Answer on Stackoverflow
Solution 5 - .NetBramView Answer on Stackoverflow
Solution 6 - .NetJackView Answer on Stackoverflow
Solution 7 - .NetTümay TuzcuView Answer on Stackoverflow
Solution 8 - .NetSteztricView Answer on Stackoverflow