Click here to Skip to main content
15,881,455 members
Articles / Programming Languages / C#
Tip/Trick

XmlSerializer doesn't work with Dictionaries. Oh, and it has problems with KeyValuePairs too.

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
13 Jan 2012CPOL1 min read 45.7K   3   2
XmlSerializer complains if you try to serialize anything which implements IDictionary. This provides a way of serialising them which (if not capable of restoring the exact dictionary content) restores the actual field contents in a new dictionary.
All I wanted to do was simple: add a setting to the application config which held a list of the most recently used file names, complete with a user created template to process the file - so they didn't have to repeat work.

Since it is going in the config file (which is XML), it made sense to use the XmlSerializer. Easy:
C#
Dictionary<string, string> recentFiles;
...
xml.Serialize(memoryStream, recentFiles);

Ah. IDictionary not supported. MSDN says: "The XmlSerializer cannot process classes implementing the IDictionary interface. This was partly due to schedule constraints and partly due to the fact that a hashtable does not have a counterpart in the XSD type system. The only solution is to implement a custom hashtable that does not implement the IDictionary interface."
In layman speak: "We ran out of time".

OK, an IDictionary is a with-frills List of KeyValuePairs - I will convert it and rebuild...

XmlSerializer does not complain about KeyValuePair items. Great! Only problem is that it stores everything except the actual and / or value information...

C#
KeyValuePair<string, string> kvp = new KeyValuePair<string, string>("My Key", "MyValue");
using (MemoryStream xml = new MemoryStream())
    {
    var serializer = new XmlSerializer(typeof(KeyValuePair<string, string>));
    serializer.Serialize(xml, kvp);
    xml.Seek(0, 0);
    byte[] bytes = xml.GetBuffer();
    string serialized = System.Text.Encoding.ASCII.GetString(bytes);
    }

Generates:
HTML
<KeyValuePairOfStringString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

So, a brute force and ignorance approach: create a new class, that can do it:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.IO;
using System.Xml.Serialization;

namespace MyNamespace
    {
    /// <summary>
    /// Serialisable KeyValuePair
    /// </summary>
    [Serializable]
    public class SerialPair<K, V>
        {
        #region Properties
        /// <summary>
        /// Key
        /// </summary>
        public K Key { get; set; }
        /// <summary>
        /// Value
        /// </summary>
        public V Value { get; set; }
        #endregion
        #region Constructors
        /// <summary>
        /// Default constructor
        /// Required, or XmlSerializer complains.
        /// </summary>
        public SerialPair()
            {
            }
        /// <summary>
        /// Construct a SerialPair from a KeyValuePair of the same types.
        /// </summary>
        /// <param name="kvp"></param>
        public SerialPair(KeyValuePair<K, V> kvp)
            {
            Key = kvp.Key;
            Value = kvp.Value;
            }
        /// <summary>
        /// Construct a SerialPair from a Key and Value of the same types.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public SerialPair(K key, V value)
            {
            Key = key;
            Value = value;
            }
        /// <summary>
        /// Static constructor - allows as quick as possible detection of 
        /// non-serializable parameters.
        /// </summary>
        static SerialPair()
            {
            if (!typeof(K).IsSerializable && !(typeof(K) is ISerializable))
                {
                throw new InvalidOperationException("A serializable Type is required");
                }
            if (!typeof(V).IsSerializable && !(typeof(V) is ISerializable))
                {
                throw new InvalidOperationException("A serializable Type is required");
                }
            }
        #endregion
        #region Public Methods
        /// <summary>
        /// Regenerate a Dictionary from a serialised string
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static Dictionary<K, V> DeserializeDictionary(string s)
            {
            Dictionary<K, V> regeneratedTemplates;
            List<SerialPair<K, V>> list;
            byte[] combinedBytes = System.Text.Encoding.ASCII.GetBytes(s);
            using (MemoryStream sr = new MemoryStream(combinedBytes))
                {
                XmlSerializer deserializer = new XmlSerializer(typeof(List<SerialPair<K, V>>));
                list = (List<SerialPair<K, V>>)deserializer.Deserialize(sr);
                regeneratedTemplates = new Dictionary<K, V>();
                foreach (SerialPair<K, V> pair in list)
                    {
                    regeneratedTemplates.Add(pair.Key, pair.Value);
                    }
                }
            return regeneratedTemplates;
            }
        /// <summary>
        /// Generate a serialized string from a Dictionary.
        /// </summary>
        /// <param name="dict"></param>
        /// <returns></returns>
        public static string SerializeDictionary(Dictionary<K, V> dict)
            {
            string s;
            List<SerialPair<K, V>> list = new List<SerialPair<K, V>>(dict.Count);
            foreach (KeyValuePair<K, V> kvp in dict.ToArray())
                {
                SerialPair<K, V> sp = new SerialPair<K, V>(kvp);
                list.Add(sp);
                }
            using (MemoryStream xml = new MemoryStream())
                {
                XmlSerializer serializer = new XmlSerializer(typeof(List<SerialPair<K, V>>));
                serializer.Serialize(xml, list);
                xml.Seek(0, 0);
                byte[] bytes = xml.GetBuffer();
                s = System.Text.Encoding.ASCII.GetString(bytes);
                }
            return s;
            }
        #endregion
        }
    }


There is probably a much, much nicer way to do this (I could have used a collection that XmlSerializer does support, for example) but at least this way I won't forget that it doesn't work and try using it again...

[edit]Error in XML Comment to SerialPair(K, V) constructor caused compilation warning. Fixed - OriginalGriff[/edit]

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO
Wales Wales
Born at an early age, he grew older. At the same time, his hair grew longer, and was tied up behind his head.
Has problems spelling the word "the".
Invented the portable cat-flap.
Currently, has not died yet. Or has he?

Comments and Discussions

 
GeneralYou can also implement IXmlSerializer on some custom SerialP... Pin
danlobo13-Jan-12 0:44
danlobo13-Jan-12 0:44 
GeneralRe: That is a good idea! I hadn't though about that, I'll have a... Pin
OriginalGriff13-Jan-12 1:01
mveOriginalGriff13-Jan-12 1:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.