.net dictionary xml-serialization appsettings

¿Por qué no hay un diccionario serializable en XML en.NET?



dictionary xml-serialization (8)

Necesito un diccionario serializable en XML. En realidad, ahora tengo dos programas bastante diferentes que necesitan uno. Me sorprendió bastante ver que .NET no tiene uno. Hice la pregunta en otro lado y obtuve respuestas sarcásticas. No entiendo por qué es una pregunta estúpida.

¿Puede alguien aclararme, dada la dependencia de varias características de .NET en la serialización XML, por qué no hay un diccionario serializable en XML? Con suerte, también puede explicar por qué algunas personas consideran que es una pregunta tonta. Supongo que debo estar perdiendo algo fundamental y espero que puedas llenar los vacíos.


¡Usa DataContractSerializer! Vea la muestra a continuación.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.Serialization; using System.Xml; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { A a = new A(); a.Value = 1; B b = new B(); b.Value = "SomeValue"; Dictionary<A, B> d = new Dictionary<A,B>(); d.Add(a, b); DataContractSerializer dcs = new DataContractSerializer(typeof(Dictionary<A, B>)); StringBuilder sb = new StringBuilder(); using (XmlWriter xw = XmlWriter.Create(sb)) { dcs.WriteObject(xw, d); } string xml = sb.ToString(); } } public class A { public int Value { get; set; } } public class B { public string Value { get; set; } } }

El código anterior produce el siguiente xml:

<?xml version="1.0" encoding="utf-16"?> <ArrayOfKeyValueOfABHtQdUIlS xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays"> <KeyValueOfABHtQdUIlS> <Key xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1"> <d3p1:Value>1</d3p1:Value> </Key> <Value xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1"> <d3p1:Value>SomeValue</d3p1:Value> </Value> </KeyValueOfABHtQdUIlS> </ArrayOfKeyValueOfABHtQdUIlS>


Agregaron uno en .NET 3.0. Si puede, agregue una referencia a System.Runtime.Serialization y busque System.Xml.XmlDictionary, System.Xml.XmlDictionaryReader y System.Xml.XmlDictionaryWriter.

Estoy de acuerdo en que no se encuentra en un lugar particularmente reconocible.


Cree uno propio :-), la función de solo lectura es una bonificación, pero si necesita una clave que no sea una cadena, la clase necesita algunas modificaciones ...

namespace MyNameSpace { [XmlRoot("SerializableDictionary")] public class SerializableDictionary : Dictionary<String, Object>, IXmlSerializable { internal Boolean _ReadOnly = false; public Boolean ReadOnly { get { return this._ReadOnly; } set { this.CheckReadOnly(); this._ReadOnly = value; } } public new Object this[String key] { get { Object value; return this.TryGetValue(key, out value) ? value : null; } set { this.CheckReadOnly(); if(value != null) { base[key] = value; } else { this.Remove(key); } } } internal void CheckReadOnly() { if(this._ReadOnly) { throw new Exception("Collection is read only"); } } public new void Clear() { this.CheckReadOnly(); base.Clear(); } public new void Add(String key, Object value) { this.CheckReadOnly(); base.Add(key, value); } public new void Remove(String key) { this.CheckReadOnly(); base.Remove(key); } public XmlSchema GetSchema() { return null; } public void ReadXml(XmlReader reader) { Boolean wasEmpty = reader.IsEmptyElement; reader.Read(); if(wasEmpty) { return; } while(reader.NodeType != XmlNodeType.EndElement) { if(reader.Name == "Item") { String key = reader.GetAttribute("Key"); Type type = Type.GetType(reader.GetAttribute("TypeName")); reader.Read(); if(type != null) { this.Add(key, new XmlSerializer(type).Deserialize(reader)); } else { reader.Skip(); } reader.ReadEndElement(); reader.MoveToContent(); } else { reader.ReadToFollowing("Item"); } reader.ReadEndElement(); } public void WriteXml(XmlWriter writer) { foreach(KeyValuePair<String, Object> item in this) { writer.WriteStartElement("Item"); writer.WriteAttributeString("Key", item.Key); writer.WriteAttributeString("TypeName", item.Value.GetType().AssemblyQualifiedName); new XmlSerializer(item.Value.GetType()).Serialize(writer, item.Value); writer.WriteEndElement(); } } } }


Esta es mi implementación.

using System; using System.Collections.Generic; using System.Text; using System.Xml.Serialization; using System.Xml.Schema; using System.Xml; namespace Rubik.Staging { [XmlSchemaProvider("GetInternalSchema")] public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable { #region IXmlSerializable Members private const string ns = "http://www.rubik.com.tr/staging"; public static XmlQualifiedName GetInternalSchema(XmlSchemaSet xs) { bool keyIsSimple = (typeof(TKey).IsPrimitive || typeof(TKey) == typeof(string)); bool valueIsSimple = (typeof(TValue).IsPrimitive || typeof(TValue) == typeof(string)); XmlSchemas schemas = new XmlSchemas(); XmlReflectionImporter importer = new XmlReflectionImporter(ns); importer.IncludeType(typeof(TKey)); importer.IncludeType(typeof(TValue)); XmlTypeMapping keyMapping = importer.ImportTypeMapping(typeof(TKey)); XmlTypeMapping valueMapping = importer.ImportTypeMapping(typeof(TValue)); XmlSchemaExporter exporter = new XmlSchemaExporter(schemas); if(!keyIsSimple) exporter.ExportTypeMapping(keyMapping); if(!valueIsSimple) exporter.ExportTypeMapping(valueMapping); XmlSchema schema = (schemas.Count == 0 ? new XmlSchema() : schemas[0]); schema.TargetNamespace = ns; XmlSchemaComplexType type = new XmlSchemaComplexType(); type.Name = "DictionaryOf" + keyMapping.XsdTypeName + "And" + valueMapping.XsdTypeName; XmlSchemaSequence sequence = new XmlSchemaSequence(); XmlSchemaElement item = new XmlSchemaElement(); item.Name = "Item"; XmlSchemaComplexType itemType = new XmlSchemaComplexType(); XmlSchemaSequence itemSequence = new XmlSchemaSequence(); XmlSchemaElement keyElement = new XmlSchemaElement(); keyElement.Name = "Key"; keyElement.MaxOccurs = 1; keyElement.MinOccurs = 1; XmlSchemaComplexType keyType = new XmlSchemaComplexType(); XmlSchemaSequence keySequence = new XmlSchemaSequence(); XmlSchemaElement keyValueElement = new XmlSchemaElement(); keyValueElement.Name = keyMapping.ElementName; keyValueElement.SchemaTypeName = new XmlQualifiedName(keyMapping.XsdTypeName, keyMapping.XsdTypeNamespace); keyValueElement.MinOccurs = 1; keyValueElement.MaxOccurs = 1; keySequence.Items.Add(keyValueElement); keyType.Particle = keySequence; keyElement.SchemaType = keyType; itemSequence.Items.Add(keyElement); XmlSchemaElement valueElement = new XmlSchemaElement(); valueElement.Name = "Value"; valueElement.MaxOccurs = 1; valueElement.MinOccurs = 1; XmlSchemaComplexType valueType = new XmlSchemaComplexType(); XmlSchemaSequence valueSequence = new XmlSchemaSequence(); XmlSchemaElement valueValueElement = new XmlSchemaElement(); valueValueElement.Name = valueMapping.ElementName; valueValueElement.SchemaTypeName = new XmlQualifiedName(valueMapping.XsdTypeName, valueMapping.XsdTypeNamespace); valueValueElement.MinOccurs = 1; valueValueElement.MaxOccurs = 1; valueSequence.Items.Add(valueValueElement); valueType.Particle = valueSequence; valueElement.SchemaType = valueType; itemSequence.Items.Add(valueElement); itemType.Particle = itemSequence; item.SchemaType = itemType; sequence.Items.Add(item); type.Particle = sequence; schema.Items.Add(type); xs.XmlResolver = new XmlUrlResolver(); xs.Add(schema); return new XmlQualifiedName(type.Name, ns); } 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 #region IXmlSerializable Members public XmlSchema GetSchema() { return null; } #endregion } }


Lo que pasa con la serialización XML es que no se trata solo de crear una secuencia de bytes. También se trata de crear un esquema XML con el que esta secuencia de bytes validaría. No hay una buena manera en XML Schema para representar un diccionario. Lo mejor que puedes hacer es mostrar que hay una clave única.

Siempre puede crear su propio contenedor, por ejemplo, One Way to Serialize Dictionaries .


Sé que esto ha sido respondido antes, pero como tengo una forma (código) muy concisa para hacer la serialización IDictionary con la clase DataContractSerializer (utilizada por WCF, pero podría y debería usarse en cualquier lugar) no pude resistirme a contribuir aquí:

public static class SerializationExtensions { public static string Serialize<T>(this T obj) { var serializer = new DataContractSerializer(obj.GetType()); using (var writer = new StringWriter()) using (var stm = new XmlTextWriter(writer)) { serializer.WriteObject(stm, obj); return writer.ToString(); } } public static T Deserialize<T>(this string serialized) { var serializer = new DataContractSerializer(typeof(T)); using (var reader = new StringReader(serialized)) using (var stm = new XmlTextReader(reader)) { return (T)serializer.ReadObject(stm); } } }

Esto funciona perfectamente en .NET 4 y también debería funcionar en .NET 3.5, aunque todavía no lo he probado.

ACTUALIZACIÓN: ¡ No funciona en .NET Compact Framework (ni siquiera NETCF 3.7 para Windows Phone 7) ya que el DataContractSerializer no es compatible!

Hice la transmisión en secuencia porque era más conveniente para mí, aunque podría haber introducido una serialización de menor nivel a Stream y luego usarla para serializar en cadenas, pero tiendo a generalizar solo cuando sea necesario (al igual que la optimización prematura es mala , entonces es una generalización prematura ...)

El uso es muy simple:

// dictionary to serialize to string Dictionary<string, object> myDict = new Dictionary<string, object>(); // add items to the dictionary... myDict.Add(...); // serialization is straight-forward string serialized = myDict.Serialize(); ... // deserialization is just as simple Dictionary<string, object> myDictCopy = serialized.Deserialize<Dictionary<string,object>>();

myDictCopy será una copia textual de myDict.

También notará que los métodos genéricos proporcionados podrán serializar cualquier tipo (a lo mejor de mi conocimiento) ya que no está limitado a interfaces IDictionary, puede ser realmente cualquier tipo genérico T.

Espero que ayude a alguien por ahí!


Sé que esto ya se ha hecho hasta la muerte, pero esta es mi contribución. Tomé los buenos fragmentos de las soluciones de @Loudenvier y @Jack y escribí mi propia clase de diccionario serializable (lo siento, soy británico).

public class SerialisableDictionary<T1, T2> : Dictionary<T1, T2>, IXmlSerializable { private static DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<T1, T2>)); public void WriteXml(XmlWriter writer) { serializer.WriteObject(writer, this); } public void ReadXml(XmlReader reader) { Dictionary<T1, T2> deserialised = (Dictionary<T1, T2>)serializer.ReadObject(reader); foreach(KeyValuePair<T1, T2> kvp in deserialised) { Add(kvp.Key, kvp.Value); } } public XmlSchema GetSchema() { return null; } }

Me gusta este enfoque porque no tendrá que serializar o deserializar explícitamente nada, solo bombee toda la jerarquía de clases a través de un XmlSerializer y listo.


Un ayudante genérico para agregar rápidamente IXmlSerializable a cualquier diccionario (existente) sin usar herencia:

using System.Xml; using System.Xml.Serialization; using System.Collections.Generic; namespace GameSpace { public class XmlSerializerForDictionary { public struct Pair<TKey,TValue> { public TKey Key; public TValue Value; public Pair(KeyValuePair<TKey,TValue> pair) { Key = pair.Key; Value = pair.Value; }//method }//struct public static void WriteXml<TKey,TValue>(XmlWriter writer, IDictionary<TKey,TValue> dict) { var list = new List<Pair<TKey,TValue>>(dict.Count); foreach (var pair in dict) { list.Add(new Pair<TKey,TValue>(pair)); }//foreach var serializer = new XmlSerializer(list.GetType()); serializer.Serialize(writer, list); }//method public static void ReadXml<TKey, TValue>(XmlReader reader, IDictionary<TKey, TValue> dict) { reader.Read(); var serializer = new XmlSerializer(typeof(List<Pair<TKey,TValue>>)); var list = (List<Pair<TKey,TValue>>)serializer.Deserialize(reader); foreach (var pair in list) { dict.Add(pair.Key, pair.Value); }//foreach reader.Read(); }//method }//class }//namespace

Y un práctico diccionario genérico serializable:

using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using System.Collections.Generic; namespace GameSpace { public class SerializableDictionary<TKey,TValue> : Dictionary<TKey,TValue>, IXmlSerializable { public virtual void WriteXml(XmlWriter writer) { XmlSerializerForDictionary.WriteXml(writer, this); }//method public virtual void ReadXml(XmlReader reader) { XmlSerializerForDictionary.ReadXml(reader, this); }//method public virtual XmlSchema GetSchema() { return null; }//method }//class }//namespace