Cómo serializar / deserializar a 'Diccionario' desde XML personalizado no usando XElement?


Tener vacío Dictionary<int, string> cómo llenarlo con claves y valores de XML como

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

Y serializarlo de nuevo en XML no utilizando XElement?

Author: David, 2012-09-23

9 answers

Con la ayuda de una clase temporal item

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

Ejemplo De Diccionario:

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

.

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

Serialización

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

Deserialización

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

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

Así es como se puede hacer usando XElement, si cambias de opinión.

Serialización

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

Deserialización

XElement xElem2 = XElement.Parse(xml); //XElement.Load(...)
var newDict = xElem2.Descendants("item")
                    .ToDictionary(x => (int)x.Attribute("id"), x => (string)x.Attribute("value"));
 91
Author: L.B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-23 20:59:43

Paul Welter ASP.NET blog tiene un diccionario que es serializable. Pero no utiliza atributos. Explicaré por qué debajo del código.

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
}

Primero, hay un gotcha con este código. Digamos que lees un diccionario de otra fuente que tiene esto:

<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>

Esto lanzará una excepción en de-seariazation porque solo puede tener una clave para un diccionario.


La razón por la que debe usar un XElement en un diccionario seriazed es que el diccionario no está definido como Dictionary<String,String>, un diccionario es Dictionary<TKey,TValue>.

Para ver el problema, pregúntate a ti mismo: Digamos que tenemos un TValue que se serializa en algo que usa Elementos que se describe a sí mismo como XML (digamos un diccionario de diccionarios Dictionary<int,Dictionary<int,string>> (no es tan raro de un patrón, es una tabla de búsqueda)), ¿cómo representaría tu atributo only version un diccionario enteramente dentro de un atributo?

 26
Author: Scott Chamberlain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-05 16:59:35

Los diccionarios no son serializables en C# por defecto, no se por qué, pero parece haber sido una elección de diseño.

En este momento, recomiendo usar Json.NET para convertirlo a JSON y desde allí a un diccionario (y viceversa). A menos que realmente necesites el XML, te recomiendo usar JSON completamente.

 5
Author: Manuel Schweigert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-23 17:07:20

Basado en la respuesta de L. B.

Uso:

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

Clase genérica:

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());
        }
    }
}
 3
Author: Jbjstam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-23 15:43:18

Tengo una estructura 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, "") + "]";
    }
}

Entonces, la serialización XML de una propiedad Dictionary es por:

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

Solo la propiedad debe ser el array, no la Lista.

 3
Author: xmedeko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-23 07:10:17

Escriba una clase A, que contenga una matriz de clase B. La clase B debe tener una propiedad id y una propiedad value. Deserializar el xml a la clase A. Convertir la matriz en A al diccionario deseado.

Para serializar el diccionario conviértalo en una instancia de clase A, y serializar...

 2
Author: erikH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-23 18:06:34

Utilizo clases serializables para la comunicación WCF entre diferentes módulos. A continuación se muestra un ejemplo de clase serializable que sirve como DataContract también. Mi enfoque es utilizar el poder de LINQ para convertir el Diccionario en lista serializable lista fuera de la caja de KeyValuePair:

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

    namespace MyFirm.Common.Data
    {
        [DataContract]
        [Serializable]
        public class SerializableClassX
        {
            // since the Dictionary<> class is not serializable,
            // we convert it to the List<KeyValuePair<>>
            [XmlIgnore]
            public Dictionary<string, int> DictionaryX 
            {
                get
                {
                    return  SerializableList == null ? 
                            null :
                            SerializableList.ToDictionary(item => item.Key, item => item.Value);
                }

                set
                {
                    SerializableList =  value == null ?
                                        null :
                                        value.ToList();
                }
            }

            [DataMember]
            [XmlArray("SerializableList")]
            [XmlArrayItem("Pair")]
            public List<KeyValuePair<string, int>> SerializableList { get; set; }
        }
    }

El uso es sencillo: Asigno un diccionario al campo de diccionario de mi objeto de datos - DictionaryX. La serialización es soportada dentro del SerializableClassX mediante la conversión de diccionario asignado en la Lista serializable de KeyValuePair:

    // create my data object
    SerializableClassX SerializableObj = new SerializableClassX(param);

    // this will call the DictionaryX.set and convert the '
    // new Dictionary into SerializableList
    SerializableObj.DictionaryX = new Dictionary<string, int>
    {
        {"Key1", 1},
        {"Key2", 2},
    };
 1
Author: Michael G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-22 15:18:11

Puede usar ExtendedXmlSerializer. Si tienes una clase:

public class TestClass
{
    public Dictionary<int, string> Dictionary { get; set; }
}

Y crear una instancia de esta clase:

var obj = new TestClass
{
    Dictionary = new Dictionary<int, string>
    {
        {1, "First"},
        {2, "Second"},
        {3, "Other"},
    }
};

Puede serializar este objeto usando ExtendedXmlSerializer:

var serializer = new ConfigurationContainer()
    .UseOptimizedNamespaces() //If you want to have all namespaces in root element
    .Create();

var xml = serializer.Serialize(
    new XmlWriterSettings { Indent = true }, //If you want to formated xml
    obj);

El xml de salida se verá como:

<?xml version="1.0" encoding="utf-8"?>
<TestClass xmlns:sys="https://extendedxmlserializer.github.io/system" xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples;assembly=ExtendedXmlSerializer.Samples">
  <Dictionary>
    <sys:Item>
      <Key>1</Key>
      <Value>First</Value>
    </sys:Item>
    <sys:Item>
      <Key>2</Key>
      <Value>Second</Value>
    </sys:Item>
    <sys:Item>
      <Key>3</Key>
      <Value>Other</Value>
    </sys:Item>
  </Dictionary>
</TestClass>

Puede instalar ExtendedXmlSerializer desde nuget o ejecutar el siguiente comando:

Install-Package ExtendedXmlSerializer
 1
Author: Wojtpl2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-06 10:44:29

Hay una manera fácil con Sharpeserializer (código abierto) :

Http://www.sharpserializer.com/

Puede serializar/deserializar directamente el diccionario.

No hay necesidad de marcar su objeto con ningún atributo, ni tiene que dar el tipo de objeto en el método Serialize (Ver aquí ).

Para instalar a través de nuget: Install-package sharpserializer

Entonces es muy simple:

Hello World (desde el sitio web oficial):

// create fake obj
var obj = createFakeObject();

// create instance of sharpSerializer
// with standard constructor it serializes to xml
var serializer = new SharpSerializer();

// serialize
serializer.Serialize(obj, "test.xml");

// deserialize
var obj2 = serializer.Deserialize("test.xml");
 0
Author: Malick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-29 06:33:39