2012-09-14 3 views
13

C'è un modo per serializzare coppie chiave/valore (preferibilmente fortemente tipizzate, ma possibilmente anche provenienti da un dizionario) nel formato desiderato di seguito?Come serializzare una raccolta/dizionario di oggetti nel valore <key></key>

public List<Identifier> Identifiers = new List<Identifiers>(); 

public class Identifier 
{ 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 

serializza Questo normalmente il seguente:

<Identifiers> 
    <Identifier> 
    <Name>somename</Name> 
    <Description>somedescription</Description> 
    </Identifier> 
    <Identifier> 
    ... 
    </Identifier> 
</Identifiers> 

L'altro approccio possibile stavamo pensando è quella di utilizzare una tabella hash/dizionario:

public Dictionary<string, string> Identifiers = new Dictionary<string,string> 
{ 
    { "somename", "somedescription"}, 
    { "anothername", "anotherdescription" } 
}; 

Ma questo sarà o richiederà un Dizionario serializzato personalizzato o personalizzato XmlWriter.

L'uscita che vorremmo raggiungere è:

<Identifiers> 
    <somename>somedescription</somename> 
    <anothername>anotherdescription</anothername> 
</Identifiers> 

Quindi stiamo alla ricerca di esempi di codice su come meglio affrontare questo per ottenere il risultato che desideriamo.

Modifica: Forse dovrei spiegare meglio. Sappiamo già come serializzare gli oggetti. Quello che stiamo cercando è la risposta a un particolare tipo di serializzazione ... io espandere la domanda di cui sopra

+0

Dai un'occhiata a questo http://www.dacris.com/blog/2010/07/31/c-serializable-dictionary-a-working-example/ – Chatumbabub

risposta

7

Questa è una risposta difficile in quanto non si chiarisce veramente cosa significa "meglio" per voi.

più veloce sarebbe probabilmente la scrittura cruda come stringhe:

var sb = new StringBuilder(); 
sb.Append("<identifiers>"); 
foreach(var pair in identifiers) 
{ 
    sb.AppendFormat("<{0}>{1}</{0}>", pair.Key, pair.Value); 
} 
sb.Append("</identifiers>"); 

Ovviamente questo non gestire qualsiasi fuggire in XML, ma poi che potrebbe non essere un problema, dipende interamente il contenuto del dizionario.

Che dire di poche righe di codice? Se questo è il tuo requisito, probabilmente L.B.'s Linq to XML answer's è il migliore.

Che dire di impronta di memoria minima? Lì guarderei il lancio dello e la creazione della tua classe serializzabile che elimina il sovraccarico dell'hash e la funzionalità di raccolta a favore della semplice memorizzazione di nome e valore. Potrebbe essere anche il più veloce.

Se codice semplicità è il vostro requisito allora come sull'utilizzo dynamic o anonimi tipo invece di Dictionary?

var anonType = new 
{ 
    somename = "somedescription", 
    anothername = "anotherdescription" 
} 

// Strongly typed at compile time 
anonType.anothername = "new value"; 

In questo modo non a che fare con 'corde magici' per i nomi delle proprietà della tua collezione - sarà fortemente tipizzato nel codice (se questo è importante per voi).

Tuttavia i tipi anonimi non hanno costruito in serialiser - dovreste scrivere qualcosa per te, utilizzare uno dei manyopensourcealternatives o addirittura utilizzare il XmlMediaTypeFormatter.

Ci sono carichi di di modi per fare ciò, quale è il migliore dipende da come lo si utilizzerà.

+0

Ottima risposta, molte opzioni da considerare. Grazie – mwjackson

0

È possibile utilizzare C#XML serializzazione:

private static String SerializeObject<T>(T myObj, bool format) { 
    try { 
     String xmlizedString = null; 
     MemoryStream memoryStream = new MemoryStream(); 
     XmlSerializer xs = null; 
     XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8); 
     if (format) 
      xmlTextWriter.Formatting = Formatting.Indented; 

     xs = new XmlSerializer(typeof(T), "MyXmlData"); 

     xs.Serialize(xmlTextWriter, myObj); 

     memoryStream = (MemoryStream)xmlTextWriter.BaseStream; 
     //eventually 
     xmlizedString = UTF8ByteArrayToString(memoryStream.ToArray()); 
     return xmlizedString; 
    } 
    catch (Exception e) { 
     //return e.ToString(); 
     throw; 
    } 
} 

(Credit: voce del blog Serialize and deserialize objects as Xml using generic types in C# 2.0 .)

Per serializzare un Dictionary, suggerirei di convertirlo in un elenco di coppie (eventualmente con LINQ), perché non è serializzabile.

Controllare anche Controlling XML Serialization Using Attributes per modificare i nomi delle voci.

OK, dopo il tuo chiarimento, il primo compito, assurdamente difficile (o non fattibile) che mi viene in mente, è di modificare in qualche modo a livello di codice il Type degli elementi per riflettere il nome del tag utilizzando il serializzatore standard. Non so se funzionerà comunque.

+0

Per favore leggi la mia modifica – mwjackson

+0

ok, io esaminare il problema, ho visto qualcosa di simile prima, ho solo bisogno di trovare dove – Gabber

+0

mi dispiace per l'equivoco – Gabber

3

Qualche tempo fa ho avuto un problema simile. Ho finito per usare questo (tratto da here)

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

[Serializable()] 
public class SerializableDictionary<TKey, TVal> : Dictionary<TKey, TVal>, IXmlSerializable, ISerializable 
{ 
     #region Constants 
     private const string DictionaryNodeName = "Dictionary"; 
     private const string ItemNodeName = "Item"; 
     private const string KeyNodeName = "Key"; 
     private const string ValueNodeName = "Value"; 
     #endregion 
     #region Constructors 
     public SerializableDictionary() 
     { 
     } 

     public SerializableDictionary(IDictionary<TKey, TVal> dictionary) 
      : base(dictionary) 
     { 
     } 

     public SerializableDictionary(IEqualityComparer<TKey> comparer) 
      : base(comparer) 
     { 
     } 

     public SerializableDictionary(int capacity) 
      : base(capacity) 
     { 
     } 

     public SerializableDictionary(IDictionary<TKey, TVal> dictionary, IEqualityComparer<TKey> comparer) 
      : base(dictionary, comparer) 
     { 
     } 

     public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) 
      : base(capacity, comparer) 
     { 
     } 

     #endregion 
     #region ISerializable Members 

     protected SerializableDictionary(SerializationInfo info, StreamingContext context) 
     { 
      int itemCount = info.GetInt32("ItemCount"); 
      for (int i = 0; i < itemCount; i++) 
      { 
       KeyValuePair<TKey, TVal> kvp = (KeyValuePair<TKey, TVal>)info.GetValue(String.Format("Item{0}", i), typeof(KeyValuePair<TKey, TVal>)); 
       this.Add(kvp.Key, kvp.Value); 
      } 
     } 

     void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) 
     { 
      info.AddValue("ItemCount", this.Count); 
      int itemIdx = 0; 
      foreach (KeyValuePair<TKey, TVal> kvp in this) 
      { 
       info.AddValue(String.Format("Item{0}", itemIdx), kvp, typeof(KeyValuePair<TKey, TVal>)); 
       itemIdx++; 
      } 
     } 

     #endregion 
     #region IXmlSerializable Members 

     void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) 
     { 
      //writer.WriteStartElement(DictionaryNodeName); 
      foreach (KeyValuePair<TKey, TVal> kvp in this) 
      { 
       writer.WriteStartElement(ItemNodeName); 
       writer.WriteStartElement(KeyNodeName); 
       KeySerializer.Serialize(writer, kvp.Key); 
       writer.WriteEndElement(); 
       writer.WriteStartElement(ValueNodeName); 
       ValueSerializer.Serialize(writer, kvp.Value); 
       writer.WriteEndElement(); 
       writer.WriteEndElement(); 
      } 
      //writer.WriteEndElement(); 
     } 

     void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) 
     { 
      if (reader.IsEmptyElement) 
      { 
       return; 
      } 

      // Move past container 
      if (!reader.Read()) 
      { 
       throw new XmlException("Error in Deserialization of Dictionary"); 
      } 

      //reader.ReadStartElement(DictionaryNodeName); 
      while (reader.NodeType != XmlNodeType.EndElement) 
      { 
       reader.ReadStartElement(ItemNodeName); 
       reader.ReadStartElement(KeyNodeName); 
       TKey key = (TKey)KeySerializer.Deserialize(reader); 
       reader.ReadEndElement(); 
       reader.ReadStartElement(ValueNodeName); 
       TVal value = (TVal)ValueSerializer.Deserialize(reader); 
       reader.ReadEndElement(); 
       reader.ReadEndElement(); 
       this.Add(key, value); 
       reader.MoveToContent(); 
      } 
      //reader.ReadEndElement(); 

      reader.ReadEndElement(); // Read End Element to close Read of containing node 
     } 

     System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() 
     { 
      return null; 
     } 

     #endregion 
     #region Private Properties 
     protected XmlSerializer ValueSerializer 
     { 
      get 
      { 
       if (valueSerializer == null) 
       { 
        valueSerializer = new XmlSerializer(typeof(TVal)); 
       } 
       return valueSerializer; 
      } 
     } 

     private XmlSerializer KeySerializer 
     { 
      get 
      { 
       if (keySerializer == null) 
       { 
        keySerializer = new XmlSerializer(typeof(TKey)); 
       } 
       return keySerializer; 
      } 
     } 
     #endregion 
     #region Private Members 
     private XmlSerializer keySerializer = null; 
     private XmlSerializer valueSerializer = null; 
     #endregion 
} 

ho appena notato @Chatumbabub fornito lo stesso link nel suo commento. Non ha fatto il trucco per te?

+0

Questo è quello che uso. Funziona piuttosto bene. – Bobson

1

Non penso che si possa fare ciò che si vuole con il "statico" XmlSerializer.Qui ci sono un paio di aiutanti che si potrebbe iniziare con un dizionario (generica o meno):

public static string Serialize(IDictionary dictionary) 
    { 
     using (StringWriter writer = new StringWriter()) 
     { 
      Serialize(writer, dictionary); 
      return writer.ToString(); 
     } 
    } 

    public static void Serialize(TextWriter writer, IDictionary dictionary) 
    { 
     if (writer == null) 
      throw new ArgumentNullException("writer"); 

     using (XmlTextWriter xwriter = new XmlTextWriter(writer)) 
     { 
      Serialize(xwriter, dictionary); 
     } 
    } 

    public static void Serialize(XmlWriter writer, IDictionary dictionary) 
    { 
     if (writer == null) 
      throw new ArgumentNullException("writer"); 

     if (dictionary == null) 
      throw new ArgumentNullException("dictionary"); 

     foreach (DictionaryEntry entry in dictionary) 
     { 
      writer.WriteStartElement(string.Format("{0}", entry.Key)); 
      writer.WriteValue(entry.Value); 
      writer.WriteEndElement(); 
     } 
    } 

Con questi aiutanti, il seguente codice:

 Dictionary<string, string> Identifiers = new Dictionary<string,string> 
     { 
      { "somename", "somedescription"}, 
      { "anothername", "anotherdescription" } 
     }; 
     Console.WriteLine(Serialize(Identifiers)); 

uscita volontà questo:

<somename>somedescription</somename><anothername>anotherdescription</anothername> 

Puoi adattarti alla tua volontà.

0

Un'opzione alternativa sarebbe utilizzare il reflection nel serializzatore personalizzato per ottenere il nome della proprietà o della variabile come pure il suo valore e utilizzarlo per creare l'XML. In questo modo non avresti bisogno di sapere cosa stavi passando o il nome delle sue proprietà. Tieni presente che l'uso del riflesso è lento, quindi se stai serializzando un numero elevato di oggetti, questa potrebbe non essere la strada da percorrere.

22

E 'facile con LINQ to XML:

Dictionary<string, string> Identifiers = new Dictionary<string,string>() 
{ 
    { "somename", "somedescription"}, 
    { "anothername", "anotherdescription" } 
}; 

XElement xElem = new XElement("Identifiers", 
           Identifiers.Select(x=>new XElement(x.Key,x.Value))); 

string xml = xElem.ToString(); //xElem.Save(.....); 

USCITA:

<Identifiers> 
    <somename>somedescription</somename> 
    <anothername>anotherdescription</anothername> 
</Identifiers> 
1

fa questo aiuto a tutti?

public class CustomDictionary<TValue> : Dictionary<string, TValue>, IXmlSerializable 
{ 
    private static readonly XmlSerializer ValueSerializer; 

    private readonly string _namespace; 

    static CustomDictionary() 
    { 
     ValueSerializer = new XmlSerializer(typeof(TValue)); 
     ValueSerializer.UnknownNode += ValueSerializerOnUnknownElement; 
    } 

    private static void ValueSerializerOnUnknownElement(object sender, XmlNodeEventArgs xmlNodeEventArgs) 
    { 
     Debugger.Break(); 
    } 

    public CustomDictionary() 
     : this("") 
    { 
    } 

    public CustomDictionary(string @namespace) 
    { 
     _namespace = @namespace; 
    } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     reader.Read(); 
     var keepGoing = true; 

     while(keepGoing) 
     { 
      if (reader.NodeType == XmlNodeType.Element) 
      { 
       this[reader.Name] = (TValue) reader.ReadElementContentAs(typeof (TValue), null); 
      } 
      else 
      { 
       keepGoing = reader.Read(); 
      } 
     } 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     foreach(var kvp in this) 
     { 
      var document = new XDocument(); 

      using(var stringWriter = document.CreateWriter()) 
      { 
       ValueSerializer.Serialize(stringWriter, kvp.Value); 
      } 

      var serializedValue = document.Root.Value; 
      writer.WriteElementString(kvp.Key, _namespace, serializedValue); 
     } 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var dict = new CustomDictionary<string> 
     { 
      {"Hello", "World"}, 
      {"Hi", "There"} 
     }; 

     var serializer = new XmlSerializer(typeof (CustomDictionary<string>)); 

     serializer.Serialize(Console.Out, dict); 
     Console.ReadLine(); 
    } 
} 
1

È possibile utilizzare DataContractSerializer per serializzare & deserialize Dictionary<string, string>.

CODICE:

Dictionary<string, string> dictionary = new Dictionary<string, string>(); 

dictionary.Add("k1", "valu1"); 
dictionary.Add("k2", "valu2"); 

System.Runtime.Serialization.DataContractSerializer serializer = new System.Runtime.Serialization.DataContractSerializer(typeof(Dictionary<string, string>)); 
System.IO.MemoryStream stream = new System.IO.MemoryStream(); 

serializer.WriteObject(stream, dictionary); 

System.IO.StreamReader reader = new System.IO.StreamReader(stream); 

stream.Position = 0; 
string xml = reader.ReadToEnd(); 
1

Un altro approccio è quello di sottoclasse XmlTextWriter e controllare l'uscita durante la serializzazione del tipo Identifier. Un po 'hacker ma potrebbe darti un'altra strada. Non richiede l'aggiunta di alcun metadata di attributo ai tipi.

public class IdentifierXmlWriter : XmlTextWriter 
{ 
    private bool isIdentifier = false; 
    private bool isName = false; 
    private bool isDescription = false; 

    private readonly string identifierElementName; 
    private readonly string nameElementName; 
    private readonly string descElementName; 

    public IdentifierXmlWriter(TextWriter w) : base(w) 
    { 
     Type identitierType = typeof (Identifier); 

     identifierElementName = (identitierType.GetCustomAttributes(typeof(XmlElementAttribute), true).FirstOrDefault() as XmlElementAttribute ?? new XmlElementAttribute("Identifier")).ElementName; 
     nameElementName = (identitierType.GetProperty("Name").GetCustomAttributes(typeof(XmlElementAttribute), true).FirstOrDefault() as XmlElementAttribute ?? new XmlElementAttribute("Name")).ElementName; 
     descElementName = (identitierType.GetProperty("Description").GetCustomAttributes(typeof(XmlElementAttribute), true).FirstOrDefault() as XmlElementAttribute ?? new XmlElementAttribute("Description")).ElementName; 
    } 

    public override void WriteStartElement(string prefix, string localName, string ns) 
    { 
     // If Identifier, set flag and ignore. 
     if (localName == identifierElementName) 
     { 
      isIdentifier = true; 
     } 
     // if inside Identifier and first occurance of Name, set flag and ignore. This will be called back with the element name in the Name's Value write call 
     else if (isIdentifier && !isName && !isDescription && localName == this.nameElementName) 
     { 
      isName = true; 
     } 
     // if inside Identifier and first occurance of Description, set flag and ignore 
     else if (isIdentifier && !isName && !isDescription && localName == this.descElementName) 
     { 
      isDescription = true; 
     } 
     else 
     { 
      // Write the element 
      base.WriteStartElement(prefix, localName, ns); 
     } 
    } 

    public override void WriteString(string text) 
    { 
     if (this.isIdentifier && isName) 
      WriteStartElement(text);   // Writing the value of the Name property - convert to Element 
     else 
      base.WriteString(text); 
    } 

    public override void WriteEndElement() 
    { 
     // Close element from the Name property - Ignore 
     if (this.isIdentifier && this.isName) 
     { 
      this.isName = false; 
      return; 
     } 

     // Cliose element from the Description - Closes element started with the Name value write 
     if (this.isIdentifier && this.isDescription) 
     { 
      base.WriteEndElement(); 
      this.isDescription = false; 
      return; 
     } 

     // Close element of the Identifier - Ignore and reset 
     if (this.isIdentifier) 
     { 
      this.isIdentifier = false; 
     } 
     else 
      base.WriteEndElement(); 
    } 
} 

     List<Identifier> identifiers = new List<Identifier>() 
              { 
               new Identifier() { Name = "somename", Description = "somedescription"}, 
               new Identifier() { Name = "anothername", Description = "anotherdescription"}, 
               new Identifier() { Name = "Name", Description = "Description"}, 
              }; 

Questo esegue il codice sopra riportato e genera il formato necessario, anche se senza interruzioni di riga e rientro.

 StringBuilder sb = new StringBuilder(); 
     using (var writer = new IdentifierXmlWriter(new StringWriter(sb))) 
     { 
      XmlSerializer xmlSerializer = new XmlSerializer(identifiers.GetType(), new XmlRootAttribute("Identifiers")); 
      xmlSerializer.Serialize(writer, identifiers); 
     } 

     Console.WriteLine(sb.ToString());