XML Serializable Generic Dictionary

XML Serializable Generic Dictionary

For some reason, the generic Dictionary in .net 2.0 is not XML serializable.  The following code snippet is a xml serializable generic dictionary.  The dictionary is serialzable by implementing the IXmlSerializable interface. 

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Xml.Serialization;

 

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

 

            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

    }

Update: Fixed bug Justin pointed out by adding an extra reader.ReadEndElement() and by checking the IsEmptyElement property.

83 Comments

  • Justin,



    Do you have an example of what will cause it to fail? I've done a lot of testing but must have missed a use case. Thanks for the heads up.



    ~ Paul

  • Try and create a class with the SerializableAttribute and properties of type string, SerializableDictionary, string (in that order).



    Make a webservice method that returns this class (with some content, meaning that string should have a value and the dirctionary should have at least two values).



    Look the the XML the webservice generates and see it's OK.



    Create a proxy to the webservice and call the webservice method you just created. The class you deserialize will have no value for the string that comes after the SerializableDictionary. (At least i think it won't). If i'm right and that issue does exist it's because you don't read the &lt;/dictionary&gt; element at the end of ReadXml.

  • Ok, I fixed the issue. I got about 30 test cases for this now. Should be fairly solid. No guarantees of course. :)



    ~ Paul

  • Hi Paul,



    Thanks for creating this, saved me time!



    Ward

  • This doesn't seem to work if you have a dictionary of custom types.



    I.E. SerializableDictionary&lt;int, MyObject&gt;

  • Brian,



    It should work as long as MyObject is Xml Serializable. Try to serialize MyObject first to see if it works.



    ~ Paul

  • Nevermind! Ignore previous comment.

  • Paul it does, my goof. Thanks this is very helpful.

  • Thanks, Paul.

    This is really neat. I have a WebMethod returning a SerializableDictionary. This works fine when I view the service in a browser, & call the method there, but somehow does not work from a fat-client.

    In fact the generated wsdl does not show the response-type as above.

    So, am not sure whether i need to do anything special to get it in my proxy?

  • Hi,

    Thanks for the code... I appreciate your effort.
    I'm having some problems though....
    1. When I used the code as is, I got this error ...
    [SerializationException: Type 'Microsoft.Exchange.HostedServices.Clients.Utilities.SerializableDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[Microsoft.Exchange.HostedServices.Clients.Membership.User, Microsoft.Exchange.HostedServices.Clients.Membership, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]' in Assembly 'Microsoft.Exchange.HostedServices.Clients.Utilities, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.]

    I marked it as [Serializable] and then I got the following error...
    {"The constructor to deserialize an object of type 'Microsoft.Exchange.HostedServices.Clients.Utilities.SerializableDictionary`2[System.String,Microsoft.Exchange.HostedServices.Clients.Membership.User]' was not found."}

    Then I added the following...
    protected SerializableDictionary(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
    {
    }

    Now there are no errors, but the serialization/deserialization didn't seem to work at all.
    ReadXml and WriteXml methods were never called...

    BTW, I'm storing SerializableDictionary object in session as follows...
    HttpContext.Current.Session["UserAccounts"] = new SerializableDictionary();
    ((SerializableDictionary)HttpContext.Current.Session["UserAccounts"])[emailAddress] = user;

    I'm stuck...
    Any help/feedback is appreciated.

    Thank you,
    Srivalli

  • A sample peice of C# code to exercise this in unit tests would appreciated.

  • Just want to let you know that your little piece works wonders, and saved me quite a few headaches.

    Thanks for sharing!

  • Great snippet for sure! Thanks for sharing it with us! I am having a problem, however, I hoped I could get some help with:

    I have a web service with a webmethod (where I expect a SerializableDictionary as a parameter). When I generate the client-proxy the reference.cs auto-generated class shows that parameter to be System.Data.Dataset instead of MyNamespace.SerializableDictionary. Any attempts thus far to generate the schema manually has lead to dead ends ...

    How can I (other than manually) Specify the schema for serializing the SerializableDisctionary so that the client "sees" a serializable dictionary as the parameter to this web method?

  • @Srivalli

    You have to call the base constructor of the Dictionary class it's inheriting to get it to work. Adding the following constructors should get it to work exactly like the real Dictionary implementation:

    public SerializableDictionary()
    : base()
    {
    }

    public SerializableDictionary(IDictionary dictionary)
    : base(dictionary)
    {
    }

    public SerializableDictionary(IEqualityComparer comparer)
    : base(comparer)
    {
    }

    public SerializableDictionary(int capacity)
    : base(capacity)
    {
    }

    public SerializableDictionary(IDictionary dictionary, IEqualityComparer comparer)
    : base(dictionary, comparer)
    {
    }

    public SerializableDictionary(int capacity, IEqualityComparer comparer)
    : base(capacity, comparer)
    {
    }

    protected SerializableDictionary(SerializationInfo info, StreamingContext context)
    : base(info, context)
    {
    }

  • Thanks for putting this out there. Worked like a charm!

  • Pingback from code-news.blogspot.com

  • In my thin client i'm unable to properly parse the xml generated by this class. It treats the SerializedDictionary object as a DataSet, but the data set is empty.

    Help!

  • Important: readonly public variables and get-only properties cannot be serialized to XML!

  • You are a genious!

    Thanks a lot.

  • Im having a problem using this with TryGetValue (trying to ensure thread safety).

    I have a SiteMapDataSource that i get a System.NullReferenceException on (CurrentNode==null).

    protected void menu_MenuItemDataBound(Object sender, MenuEventArgs e)
    {
    if (e.Item.Text == SiteMapDataSource 1.Provider.CurrentNode.Title)
    {
    e.Item.Text = "" + e.Item.Text + "";
    }
    }

    But if i just skip the first if() everything works like a charm.

    string tempCountryCode="uk";
    bool contains;
    if (page.CountryCodeDictionary.TryGetValue(tempCountryCode.ToLower(), out contains))
    {
    if (SupportRoles.isAdmin(HttpContext.Current.Request.LogonUserIdentity) || page.CountryCodeDictionary[tempCountryCode])
    {
    return true;
    }
    else
    {
    return false;
    }
    }
    else
    {
    return false;
    }

    Would be great if somebody knew what was causing this.

  • This works great. However, I am having one problem, when returning a Serializable dictionary from my web service, it is being treated as a DataSet.

    Is there any way to have this serialize as a two dimensional array instead of a dataset. This would also ensure that any type contained in the dictionary will have its type definition included in both the wsdl and the reference.cs web service proxy which is created on the client side.

  • Thanks so much. Works perfectly for what I need. Nice to see it still being found so easily years later!

  • why is :

    SerializableDictionary _testdict;
    [System.Runtime.Serialization.DataMember]
    public SerializableDictionary TestCollection
    {
    get { return _testdict; }
    set { _testdict = value; }
    }

    resulting in:

    [System.Xml.Serialization.XmlElementAttribute(Order=0)]
    public System.Data.DataSet TestCollection
    {
    get
    {
    return this.testCollectionField;
    }
    set
    {
    this.testCollectionField = value;
    }
    }

    dont want a DATASET !!! what can i change !!! (other then that this is a great class)


  • It works wonders, thanks. It saved me a lot of time.

  • I am having the same problem mentioned in several other posts on this article, that being, the class deserializes as a DataSet for some unknown reason.

    Strangely enough I have used it with success elsewhere. Am I missing something small but fundamental?

  • Using in code
    -------------------------
    string txt;
    using (StringWriter string_writer = new StringWriter())
    {
    xml_serializer.Serialize(string_writer, sd);
    txt = string_writer.ToString();
    string_writer.Close();
    }
    SerializableDictionary _sd = new SerializableDictionary();
    using (StringReader string_reader = new StringReader(txt))
    {
    _sd = (SerializableDictionary)xml_serializer.Deserialize(string_reader);
    }

  • Thanks... works a treat.

  • Thanks for the tip!

  • Really great work... thanks for sharing it!

  • This is not writing the dictionary, this is writing the contents of the dictionary. It should encapsulate the content in another element.

  • Thanks , Saved my alot of time.

  • Absolutely great! Thanks for sharing, works great!

  • To be able to deserialize the dictionary with a binary formatter add constructor:

    protected XmlDictionary(SerializationInfo info, StreamingContext context)
    : base(info, context)
    {
    }

  • I am trying to call the Add method on the Dictionary from diffrent pages, i have to create a new instance of Dictionary, and my data from first page is not reflected in the next page.

    Can you help?

  • serializable dictinary is compiled as a dataset//how can I fix this?

  • Many thanks for that!

  • i am trying to use this code but getting an error.. i am using the Dictionary While serializing this object i am getting this error .

    "Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

    class people
    {
    extendedproperties SerializableDictionary;

    }

    in this case i am adding an extended property of type 'Person' in to people. now i try to serialize the People object. My person object is serializable object. can you tell me where i am going wrong. Thanks in advance.

  • Many thanks for that!


  • Thanks! I appreciate you sharing your expertise.

  • Thanks! You sure saved me lots of times and headaches.

  • Sehr wertvolle Informationen! Empfehlen!

  • Sehr gute Seite. Ich habe es zu den Favoriten.

  • Wouldn't the below be a little more efficient

    if (reader.IsEmptyElement)
    return;

    reader.Read();

    instead of

    bool wasEmpty = reader.IsEmptyElement;
    reader.Read();

    if (wasEmpty)
    return;

  • Thanks! very useful

  • Thanks! very useful.

  • Hi,

    i encountered the same problem as Pavan, when trying to serialize "own user- objects" i also got this error:

    InvalidOperationException:
    "Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

    This error occurs because the serializer does not know the Types to be Serialized/Deserialized, but you can feed the
    serializer with the missing types:

    [XmlRoot("dictionary")]
    public class SerializableDictionary : Dictionary, IXmlSerializable
    {

    public static List AdditionalTypes = new List();
    ...

    XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
    XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue),AdditionalTypes.ToArray());

    ...

    XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
    XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue),AdditionalTypes.ToArray());

    ...

    In this snippet I included a static list of the Types to be Serialized. The list has to be initialized with the "additional" Types that are but as "Values" in the Dictionary.

    This solution work pretty well for me,

    please let me know what you think about it.

    Greetings

    Thomas

  • Spectacular sir!

    You are a gentleman and a scholar!
    You give blogs a good name!

  • Pingback from Wie sichert man am besten Programmdaten und Arrays - Seite 2 | hilpers

  • Has anyone been able to get past the weird problem of the client seeing it as a dataset?

  • The client sees it as a dataset because the GetSchema method returns null. Null is the default proxy generator representation of a dataset.

    Is there any way to return a valid schema here?

  • I'm trying to set a property to this type in my Settings.settings file, but when I browse for types, my class is not listed. If I type in the namespace/class exactly, Visual Studio tells me it is not defined. However, if I manually edit the Settings.Designer.cs file with the SerializableDictonary type (instead of the type declared in the Settings.settings file), everything works like a charm. My problem is that any changes to the Settings.settings file re-generate the Settings.Designer.cs code, destroying my changes. Does anyone have any idea why this type is not showing up in the type browser in the Settings.settings file?

    Thanks,

    Mike

  • hi there.
    this is a nice solution for serializing Dictionaries. thanks. saved me lots of time.

  • Paul,
    Thanks so much for creating and posting this! I ran into the serialization problem and immediately had visions of doing it by hand. blah. Your solution worked the first time!

  • I'm not sure why, but when using this to serialize to a string and working only with the XML fragment produced by this (i.e. a fragment of just the serialized dictionary, so was the top-level element), I had to add two reader.EOF checks in ReadXml to avoid encountering the end of the file, like so:

    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.EOF)
    {
    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();
    }
    if (!reader.EOF)
    reader.ReadEndElement();
    }

  • Works great. Thanks!

  • Thanks a lot it works fine for me

  • HI pwelter34,

    Great snippet.

    Have a problem though. I have tried to create an SerializableDictionary inside another SerializableDictionary.

    private SerializableDictionary<EnumType1, SerializableDictionary> _matrix;


    This seems to fail. Please help.

    Sundip

  • Ancak bir sorun var. Başka bir SerializableDictionary içinde bir SerializableDictionary oluşturmak için çalıştık.

    Özel SerializableDictionary <EnumType1, SerializableDictionary <EnumType2,

  • any more posts coming ?

  • thanks a very nice

  • Very nice script in XML. I will try it.

  • Thanks harita xml :)

  • в итоге: мне понравилось... а82ч

  • в конце концов: превосходно!! а82ч

  • I think u need to include the zero argument constructor in order to make it work.

  • Great tip johannes! I was facing the same issue posted by Yury - http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspx#5525609.

    Thanks once again.

  • Thank, very helpful

    Ze

  • Thanks for the code example. I got it working, but only after forcing the writer to push everything out: writer.Flush();

  • You're IOStrams (reader, writer) are not disposed!

  • The code helps me to serialize Generic Dictionary Items. Thanks for sharing.

  • In extension to Paul Welter's response on Monday, May 15, 2006 4:11 PM:

    Ensure both types (TKey and TValue) are of type System.Xml.Serialization.IXmlSerializable, use the following class declaration syntax:

    public class SerializableDictionary : Dictionary,
    IXmlSerializable
    where TKey : IXmlSerializable
    where TValue : IXmlSerializable
    {
    // ...
    }

  • SerializableDictionary is converted to dataset .... but the dataset is empty ....

    How do I solve the problem....

  • your blog is best man i loved it to read this post.

  • Thanks! very useful

  • Do you provide a license to your code? what kind of restrictions do you have on using your code for commercial purposes. Also the terms of use below points to Microsoft terms of use..Do you have copyrights on this code? Thanks

  • You saved my day Paul, god bless you!

  • thanks a love admın day paul good

  • Thanks man, it was very small and sweet explanation to understand this. It really helped me, I had similar problem as MD, but now everything is working. You people rocks

  • Thanks, this is exactly what I needed, I modded it slightly as I needed to serialize a SortedDictionary.

  • This is awesome news. A great way to greet the day.

  • looking for better and professional seo services, then please feel free to visit our website.
    ============================================================
    Pallavi

    seo services

  • thnx for sharing great article

  • Thanks! very useful...

Comments have been disabled for this content.