How to serialize/deserialize to `Dictionary<int, string>` from custom XML not using XElement?

193,874

Solution 1

With the help of a temporary item class

public class item
{
    [XmlAttribute]
    public int id;
    [XmlAttribute]
    public string value;
}

Sample Dictionary:

Dictionary<int, string> dict = new Dictionary<int, string>()
{
    {1,"one"}, {2,"two"}
};

.

XmlSerializer serializer = new XmlSerializer(typeof(item[]), 
                                 new XmlRootAttribute() { ElementName = "items" });

Serialization

serializer.Serialize(stream, 
              dict.Select(kv=>new item(){id = kv.Key,value=kv.Value}).ToArray() );

Deserialization

var orgDict = ((item[])serializer.Deserialize(stream))
               .ToDictionary(i => i.id, i => i.value);

------------------------------------------------------------------------------

Here is how it can be done using XElement, if you change your mind.

Serialization

XElement xElem = new XElement(
                    "items",
                    dict.Select(x => new XElement("item",new XAttribute("id", x.Key),new XAttribute("value", x.Value)))
                 );
var xml = xElem.ToString(); //xElem.Save(...);

Deserialization

XElement xElem2 = XElement.Parse(xml); //XElement.Load(...)
var newDict = xElem2.Descendants("item")
                    .ToDictionary(x => (int)x.Attribute("id"), x => (string)x.Attribute("value"));

Solution 2

Paul Welter's ASP.NET blog has a dictionary that is serializeable. But it does not use attributes. I will explain why below the code.

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
}

First, there is one gotcha with this code. Say you read a dictionary from another source that has this:

<dictionary>
  <item>
    <key>
      <string>key1</string>
    </key>
    <value>
      <string>value1</string>
    </value>
  </item>
  <item>
    <key>
      <string>key1</string>
    </key>
    <value>
      <string>value2</string>
    </value>
  </item>
</dictionary>

This will throw a exception on de-seariazation because you can only have one key for a dictionary.


The reason you MUST use a XElement in a seriazed dictionary is dictionary is not defined as Dictionary<String,String>, a dictionary is Dictionary<TKey,TValue>.

To see the problem, ask your self: Lets say we have a TValue that serializes in to something that uses Elements it describes itself as XML (lets say a dictionary of dictionaries Dictionary<int,Dictionary<int,string>> (not that uncommon of a pattern, it's a lookup table)), how would your Attribute only version represent a dictionary entirely inside a attribute?

Solution 3

Dictionaries are not Serializable in C# by default, I don't know why, but it seems to have been a design choice.

Right now, I'd recommend using Json.NET to convert it to JSON and from there into a dictionary (and vice versa). Unless you really need the XML, I'd recommend using JSON completely.

Solution 4

Based on L.B.'s answer.

Usage:

var serializer = new DictionarySerializer<string, string>();
serializer.Serialize("dictionary.xml", _dictionary);
_dictionary = _titleDictSerializer.Deserialize("dictionary.xml");

Generic class:

public class DictionarySerializer<TKey, TValue>
{
    [XmlType(TypeName = "Item")]
    public class Item
    {
        [XmlAttribute("key")]
        public TKey Key;
        [XmlAttribute("value")]
        public TValue Value;
    }

    private XmlSerializer _serializer = new XmlSerializer(typeof(Item[]), new XmlRootAttribute("Dictionary"));

    public Dictionary<TKey, TValue> Deserialize(string filename)
    {
        using (FileStream stream = new FileStream(filename, FileMode.Open))
        using (XmlReader reader = XmlReader.Create(stream))
        {
            return ((Item[])_serializer.Deserialize(reader)).ToDictionary(p => p.Key, p => p.Value);
        }
    }

    public void Serialize(string filename, Dictionary<TKey, TValue> dictionary)
    {
        using (var writer = new StreamWriter(filename))
        {
            _serializer.Serialize(writer, dictionary.Select(p => new Item() { Key = p.Key, Value = p.Value }).ToArray());
        }
    }
}

Solution 5

I have a struct KeyValuePairSerializable:

[Serializable]
public struct KeyValuePairSerializable<K, V>
{
    public KeyValuePairSerializable(KeyValuePair<K, V> pair)
    {
        Key = pair.Key;
        Value = pair.Value;
    }

    [XmlAttribute]
    public K Key { get; set; }

    [XmlText]
    public V Value { get; set; }

    public override string ToString()
    {
        return "[" + StringHelper.ToString(Key, "") + ", " + StringHelper.ToString(Value, "") + "]";
    }
}

Then, the XML serialization of a Dictionary property is by:

[XmlIgnore]
public Dictionary<string, string> Parameters { get; set; }

[XmlArray("Parameters")]
[XmlArrayItem("Pair")]
[DebuggerBrowsable(DebuggerBrowsableState.Never)] // not necessary
public KeyValuePairSerializable<string, string>[] ParametersXml
{
    get 
    { 
        return Parameters?.Select(p => new KeyValuePairSerializable<string, string>(p)).ToArray(); 
    }
    set
    {
        Parameters = value?.ToDictionary(i => i.Key, i => i.Value);
    }
}

Just the property must be the array, not the List.

Share:
193,874
myWallJSON
Author by

myWallJSON

Updated on July 09, 2022

Comments

  • myWallJSON
    myWallJSON almost 2 years

    Having empty Dictionary<int, string> how to fill it with keys and values from XML like

    <items>
    <item id='int_goes_here' value='string_goes_here'/>
    </items>
    

    and serialize it back into XML not using XElement?

  • Manuel Schweigert
    Manuel Schweigert over 11 years
    As far as I understood, he wants to de-/serialize XML to a dictionary. If Dictionary were serializable, you could use built-in classes like XmlSerializer for the task, but as I said, it isn't.
  • Julian50
    Julian50 about 9 years
    I use your code in a loop. And I have a memory leak. I added XmlSerializer.dispose(). but no changed... is it normal ?
  • Vippy
    Vippy about 8 years
    You deserialize by calling the "ReadXml" method.
  • Daniel McLaury
    Daniel McLaury about 7 years
    You want the WriteStartElement("item") and ReadStartElement("item") and so forth inside the foreach loop, right?
  • matra
    matra over 6 years
    Some constructors of XmlSerializer leak memory (temporary generated assembly) - you should cache them See support.microsoft.com/en-us/help/886385/…
  • Giggioz
    Giggioz almost 5 years
    @L.B is it possibile to extend this to serialize a Dictionary<string, List<string>> ? I'm not able to do it, thank you.
  • Alex Zinkevych
    Alex Zinkevych over 3 years
    Could you please provide an example on how to use this class?