web-dev-qa-db-fra.com

Comment sérialiser/désérialiser vers `Dictionary <int, string>` à partir de XML personnalisé n'utilisant pas XElement?

Dictionary<int, string> vide, comment le remplir avec des clés et des valeurs XML comme 

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

et le sérialiser en XML sans utiliser XElement?

42
myWallJSON

Avec l'aide d'une classe item temporaire

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

Exemple de dictionnaire:

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

.

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

Sérialisation

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

Désérialisation

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

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

Voici comment procéder avec XElement, si vous changez d’avis.

Sérialisation

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

Désérialisation

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

Le blog ASP.NET de Paul Welter a un dictionnaire sérialisable. Mais il n'utilise pas d'attributs. Je vais expliquer pourquoi ci-dessous le 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
}

Tout d'abord, il y a un gotcha avec ce code. Disons que vous lisez un dictionnaire d'une autre source qui a ceci:

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

Cela lève une exception sur la réduction de la mer car vous ne pouvez avoir qu'une clé pour un dictionnaire.


La raison pour laquelle vous DEVEZ utiliser un XElement dans un dictionnaire seriazed est que dictionnaire n'est pas défini par Dictionary<String,String>, mais que dictionnaire est Dictionary<TKey,TValue>.

Pour voir le problème, posez-vous la question suivante: disons que nous avons une TValue qui se sérialise en quelque chose qui utilise des éléments qu'elle décrit en tant que XML (disons un dictionnaire de dictionnaires Dictionary<int,Dictionary<int,string>> (pas aussi rare qu'un modèle, c'est une table de recherche)) , comment votre version uniquement d’attributs représenterait-elle un dictionnaire entièrement dans un attribut?

27
Scott Chamberlain

Les dictionnaires ne sont pas sérialisables en C # par défaut, je ne sais pas pourquoi, mais cela semble avoir été un choix de conception.

Pour le moment, je recommanderais d'utiliser Json.NET pour le convertir en JSON et à partir de là en dictionnaire (et vice versa). À moins que vous n'ayez vraiment besoin du XML, je vous recommande d'utiliser complètement JSON.

5
Manuel Schweigert

J'ai un 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, "") + "]";
    }
}

Ensuite, la sérialisation XML d'une propriété Dictionary est réalisée par:

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

Juste la propriété doit être le tableau, pas la liste.

3
xmedeko

Basé sur la réponse de L.B.

Usage:

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

Classe générique:

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
Jbjstam

Vous pouvez utiliser ExtendedXmlSerializer . Si vous avez un cours:

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

et créez une instance de cette classe:

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

Vous pouvez sérialiser cet objet à l'aide de 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);

La sortie xml ressemblera à:

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

Vous pouvez installer ExtendedXmlSerializer à partir de nuget ou exécuter la commande suivante:

Install-Package ExtendedXmlSerializer
2
Wojtpl2

Ecrivez une classe A qui contient un tableau de classe B. La classe B doit avoir une propriété id et une propriété value. Désérialisez le fichier XML en classe A. Convertissez le tableau de A en dictionnaire recherché. 

Pour sérialiser le dictionnaire, convertissez-le en une instance de classe A, et sérialisez ...

2
erikH

J'utilise des classes sérialisables pour la communication WCF entre différents modules . Voici un exemple de classe sérialisable qui sert également de DataContract . Mon approche consiste à utiliser la puissance de LINQ pour convertir le dictionnaire en out-of- the-box serializable List <> 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; }
        }
    }

L'utilisation est simple: j'attribue un dictionnaire au champ de dictionnaire de mon objet de données - DictionaryX. La sérialisation est prise en charge dans SerializableClassX par conversion du dictionnaire attribué en liste sérialisable <> 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
Michael G

KeyedCollection fonctionne comme un dictionnaire et est sérialisable.

Commencez par créer une classe contenant la clé et la valeur:

/// <summary>
/// simple class
/// </summary>
/// <remarks></remarks>
[Serializable()]
public class cCulture
{
    /// <summary>
    /// culture
    /// </summary>
    public string culture;

    /// <summary>
    /// Word list
    /// </summary>
    public List<string> list;

    /// <summary>
    /// status
    /// </summary>
    public string status;
}

créez ensuite une classe de type KeyedCollection et définissez une propriété de votre classe comme clé.

/// <summary>
/// keyed collection.
/// </summary>
/// <remarks></remarks>
[Serializable()]
public class cCultures : System.Collections.ObjectModel.KeyedCollection<string, cCulture>
{
    protected override string GetKeyForItem(cCulture item)
    {
        return item.culture;
    }
}

Utile pour sérialiser ce type de données.

1
Smartoweb

Il existe un moyen simple d'utiliser Sharpeserializer (open source):

http://www.sharpserializer.com/

Il peut directement sérialiser/désérialiser le dictionnaire.

Il n'est pas nécessaire de marquer votre objet avec un attribut, ni de donner le type d'objet dans la méthode Serialize (voir ici ).

Pour installer via nuget: Install-package sharpserializer

Alors c'est très simple:

Hello World (à partir du site officiel):

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