c# - newtonsoft - Serialize Class que contiene el miembro Dictionary
newtonsoft dictionary deserialize (10)
Crea un sustituto de serialización.
Ejemplo, tienes una clase con propiedad pública de tipo Diccionario.
Para admitir la serialización Xml de este tipo, cree una clase genérica de valor-clave:
public class SerializeableKeyValue<T1,T2>
{
public T1 Key { get; set; }
public T2 Value { get; set; }
}
Agregue un atributo XmlIgnore a su propiedad original:
[XmlIgnore]
public Dictionary<int, string> SearchCategories { get; set; }
Exponer una propiedad pública del tipo de matriz, que contiene una matriz de instancias de SerializableKeyValue, que se utilizan para serializar y deserializar en la propiedad SearchCategories:
public SerializeableKeyValue<int, string>[] SearchCategoriesSerializable
{
get
{
var list = new List<SerializeableKeyValue<int, string>>();
if (SearchCategories != null)
{
list.AddRange(SearchCategories.Keys.Select(key => new SerializeableKeyValue<int, string>() {Key = key, Value = SearchCategories[key]}));
}
return list.ToArray();
}
set
{
SearchCategories = new Dictionary<int, string>();
foreach (var item in value)
{
SearchCategories.Add( item.Key, item.Value );
}
}
}
Ampliando mi problema anterior , he decidido (de) serializar mi clase de archivo de configuración, que funcionó muy bien.
Ahora quiero almacenar una matriz asociativa de letras de unidad en el mapa (la clave es la letra de la unidad, el valor es la ruta de red) y he intentado usar Dictionary
, HybridDictionary
y Hashtable
para esto, pero siempre ConfigFile.Load()
el siguiente error al llamar a ConfigFile.Load()
o ConfigFile.Save()
:
Hubo un error que refleja el tipo ''App.ConfigFile''. [snip] System.NotSupportedException: No se puede serializar el miembro App.Configfile.mappedDrives [snip]
Por lo que he leído, Dictionaries y HashTables se pueden serializar, entonces, ¿qué estoy haciendo mal?
[XmlRoot(ElementName="Config")]
public class ConfigFile
{
public String guiPath { get; set; }
public string configPath { get; set; }
public Dictionary<string, string> mappedDrives = new Dictionary<string, string>();
public Boolean Save(String filename)
{
using(var filestream = File.Open(filename, FileMode.OpenOrCreate,FileAccess.ReadWrite))
{
try
{
var serializer = new XmlSerializer(typeof(ConfigFile));
serializer.Serialize(filestream, this);
return true;
} catch(Exception e) {
MessageBox.Show(e.Message);
return false;
}
}
}
public void addDrive(string drvLetter, string path)
{
this.mappedDrives.Add(drvLetter, path);
}
public static ConfigFile Load(string filename)
{
using (var filestream = File.Open(filename, FileMode.Open, FileAccess.Read))
{
try
{
var serializer = new XmlSerializer(typeof(ConfigFile));
return (ConfigFile)serializer.Deserialize(filestream);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ex.ToString());
return new ConfigFile();
}
}
}
}
Debería explorar Json.Net, bastante fácil de usar y permite que los objetos Json se deserialicen en Diccionario directamente.
ejemplo:
string json = @"{""key1"":""value1"",""key2"":""value2""}";
Dictionary<string, string> values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
Console.WriteLine(values.Count);
// 2
Console.WriteLine(values["key1"]);
// value1
Diccionarios y Hashtables no son serializables con XmlSerializer
. Por lo tanto, no puede usarlos directamente. Una solución alternativa sería utilizar el atributo XmlIgnore
para ocultar esas propiedades del serializador y exponerlas a través de una lista de pares clave-valor serializables.
PD: la construcción de un XmlSerializer
es muy costosa, por lo que siempre debe almacenarla en caché si existe la posibilidad de poder volver a utilizarla.
En lugar de usar XmlSerializer
, puede usar System.Runtime.Serialization.DataContractSerializer
. Esto puede serializar diccionarios e interfaces sin problemas.
Aquí hay un enlace a un ejemplo completo, http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/
Este artículo explica exactamente cómo manejar esto: ¿Cómo puedo ... Serializar una tabla hash en C # cuando la aplicación lo requiera?
Espero que esto sea útil
Hay una solución en Weblog de Paul Welter - XML Serializable Generic Dictionary
Por algún motivo, el diccionario genérico en .net 2.0 no es XML serializable. El siguiente fragmento de código es un diccionario genérico serializable xml. El diccionario se puede serializar implementando la interfaz IXmlSerializable.
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue>
: Dictionary<TKey, TValue>, IXmlSerializable
{
public SerializableDictionary() { }
public SerializableDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary) { }
public SerializableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer) { }
public SerializableDictionary(IEqualityComparer<TKey> comparer) : base(comparer) { }
public SerializableDictionary(int capacity) : base(capacity) { }
public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer) { }
#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
}
No puede serializar una clase que implemente IDictionary. Mira este link .
P: ¿Por qué no puedo serializar hashtables?
A: XmlSerializer no puede procesar clases implementando la interfaz de IDictionary. Esto se debió en parte a restricciones de programación y en parte debido al hecho de que una tabla hash no tiene una contraparte en el sistema de tipo XSD. La única solución es implementar una tabla hash personalizada que no implemente la interfaz IDictionary.
Entonces creo que necesitas crear tu propia versión del diccionario para esto. Mira esta otra pregunta .
Puede usar ExtendedXmlSerializer . Si tienes una clase:
public class ConfigFile
{
public String guiPath { get; set; }
public string configPath { get; set; }
public Dictionary<string, string> mappedDrives {get;set;}
public ConfigFile()
{
mappedDrives = new Dictionary<string, string>();
}
}
y crea una instancia de esta clase:
ConfigFile config = new ConfigFile();
config.guiPath = "guiPath";
config.configPath = "configPath";
config.mappedDrives.Add("Mouse", "Logitech MX Master");
config.mappedDrives.Add("keyboard", "Microsoft Natural Ergonomic Keyboard 4000");
Puede serializar este objeto usando ExtendedXmlSerializer:
ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var xml = serializer.Serialize(config);
El resultado xml se verá así:
<?xml version="1.0" encoding="utf-8"?>
<ConfigFile type="Program+ConfigFile">
<guiPath>guiPath</guiPath>
<configPath>configPath</configPath>
<mappedDrives>
<Item>
<Key>Mouse</Key>
<Value>Logitech MX Master</Value>
</Item>
<Item>
<Key>keyboard</Key>
<Value>Microsoft Natural Ergonomic Keyboard 4000</Value>
</Item>
</mappedDrives>
</ConfigFile>
Puede instalar ExtendedXmlSerializer desde nuget o ejecutar el siguiente comando:
Install-Package ExtendedXmlSerializer
Aquí está el ejemplo en línea
Quería una clase SerializableDictionary que utilizara atributos xml para clave / valor, así que he adaptado la clase de Paul Welter.
Esto produce xml como:
<Dictionary>
<Item Key="Grass" Value="Green" />
<Item Key="Snow" Value="White" />
<Item Key="Sky" Value="Blue" />
</Dictionary>"
Código:
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace DataTypes {
[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(XmlReader reader) {
XDocument doc = null;
using (XmlReader subtreeReader = reader.ReadSubtree()) {
doc = XDocument.Load(subtreeReader);
}
XmlSerializer serializer = new XmlSerializer(typeof(SerializableKeyValuePair<TKey, TValue>));
foreach (XElement item in doc.Descendants(XName.Get("Item"))) {
using(XmlReader itemReader = item.CreateReader()) {
var kvp = serializer.Deserialize(itemReader) as SerializableKeyValuePair<TKey, TValue>;
this.Add(kvp.Key, kvp.Value);
}
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer) {
XmlSerializer serializer = new XmlSerializer(typeof(SerializableKeyValuePair<TKey, TValue>));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
foreach (TKey key in this.Keys) {
TValue value = this[key];
var kvp = new SerializableKeyValuePair<TKey, TValue>(key, value);
serializer.Serialize(writer, kvp, ns);
}
}
#endregion
[XmlRoot("Item")]
public class SerializableKeyValuePair<TKey, TValue> {
[XmlAttribute("Key")]
public TKey Key;
[XmlAttribute("Value")]
public TValue Value;
/// <summary>
/// Default constructor
/// </summary>
public SerializableKeyValuePair() { }
public SerializableKeyValuePair (TKey key, TValue value) {
Key = key;
Value = value;
}
}
}
}
Pruebas unitarias:
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DataTypes {
[TestClass]
public class SerializableDictionaryTests {
[TestMethod]
public void TestStringStringDict() {
var dict = new SerializableDictionary<string, string>();
dict.Add("Grass", "Green");
dict.Add("Snow", "White");
dict.Add("Sky", "Blue");
dict.Add("Tomato", "Red");
dict.Add("Coal", "Black");
dict.Add("Mud", "Brown");
var serializer = new System.Xml.Serialization.XmlSerializer(dict.GetType());
using (var stream = new MemoryStream()) {
// Load memory stream with this objects xml representation
XmlWriter xmlWriter = null;
try {
xmlWriter = XmlWriter.Create(stream);
serializer.Serialize(xmlWriter, dict);
} finally {
xmlWriter.Close();
}
// Rewind
stream.Seek(0, SeekOrigin.Begin);
XDocument doc = XDocument.Load(stream);
Assert.AreEqual("Dictionary", doc.Root.Name);
Assert.AreEqual(dict.Count, doc.Root.Descendants().Count());
// Rewind
stream.Seek(0, SeekOrigin.Begin);
var outDict = serializer.Deserialize(stream) as SerializableDictionary<string, string>;
Assert.AreEqual(dict["Grass"], outDict["Grass"]);
Assert.AreEqual(dict["Snow"], outDict["Snow"]);
Assert.AreEqual(dict["Sky"], outDict["Sky"]);
}
}
[TestMethod]
public void TestIntIntDict() {
var dict = new SerializableDictionary<int, int>();
dict.Add(4, 7);
dict.Add(5, 9);
dict.Add(7, 8);
var serializer = new System.Xml.Serialization.XmlSerializer(dict.GetType());
using (var stream = new MemoryStream()) {
// Load memory stream with this objects xml representation
XmlWriter xmlWriter = null;
try {
xmlWriter = XmlWriter.Create(stream);
serializer.Serialize(xmlWriter, dict);
} finally {
xmlWriter.Close();
}
// Rewind
stream.Seek(0, SeekOrigin.Begin);
XDocument doc = XDocument.Load(stream);
Assert.AreEqual("Dictionary", doc.Root.Name);
Assert.AreEqual(3, doc.Root.Descendants().Count());
// Rewind
stream.Seek(0, SeekOrigin.Begin);
var outDict = serializer.Deserialize(stream) as SerializableDictionary<int, int>;
Assert.AreEqual(dict[4], outDict[4]);
Assert.AreEqual(dict[5], outDict[5]);
Assert.AreEqual(dict[7], outDict[7]);
}
}
}
}
la clase Dictionary implementa ISerializable. La definición de Diccionario de clases dada a continuación.
[DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
[System.Runtime.InteropServices.ComVisible(false)]
public class Dictionary<TKey,TValue>: IDictionary<TKey,TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>, ISerializable, IDeserializationCallback
No creo que ese sea el problema. consulte el siguiente enlace, que dice que si tiene cualquier otro tipo de datos que no es serializable, el diccionario no se serializará. http://forums.asp.net/t/1734187.aspx?Is+Dictionary+serializable+