简体   繁体   中英

How to serialize an object collection / dictionary into <key>value</key>

Is there a way to serialize key/value pairs (preferably strongly typed, but possibly also sourced from a Dictionary) into the desired format below?

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

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

This normally serializes to the following:

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

The other possible approach we were thinking about is to use a hashtable/dictionary:

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

But this will either require a custom serialized Dictionary, or a custom XmlWriter .

The output we would like to achieve is:

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

So we're looking for code samples as to how best approach this to get the output we desire.

Edit: Maybe I should explain better. We already know how to serialize objects. What we are looking for is the answer to a particular type of serialization... I'll expand the question above

It is easy with 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(.....);

OUTPUT:

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

This is hard to answer as you don't really clarify what 'best' means to you.

Fastest would probably be the raw write out as strings:

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

Obviously that's not handling any escaping to XML, but then that might not be a problem, it depends entirely on the contents of your dictionary.

What about fewest lines of code ? If that's your requirement then LB's Linq to XML answer's probably best.

What about smallest memory footprint ? There I'd look at dropping the Dictionary and creating your own serialisable class that drops the hash overhead and collection functionality in favour of just storing name and value. That might be fastest too.

If code simplicity is your requirement then how about using dynamic or anonymous types instead of Dictionary ?

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

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

That way you're not dealing with 'magic strings' for the names of properties in your collection - it will be strongly typed in your code (if that's important for you).

However anonymous types don't have a built in serialiser - you'd have to write something for yourself, use one of the many open source alternatives or even use the XmlMediaTypeFormatter .

There are loads of ways to do this, which one is best depends on how you're going to use it.

Some time ago I had a similar problem. I ended up using this (taken from 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
}

I just noticed @Chatumbabub provided the same link in his comment. Didn't that do the trick for you?

I don't think you can do what you want with the very "static" XmlSerializer. Here are a couple of helpers that you could get you started with a dictionary (generic or not):

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

With these helpers, the following code:

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

will output this:

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

You can adapt to your will.

Does this help at all?

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

You can use DataContractSerializer to serialize & deserialize Dictionary<string, string> .

CODE:

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

Another approach is to subclass the XmlTextWriter and control the output when serializing the Identifier type. A bit hackish but might give you another avenue. Doesn't require adding any attribute metadata to the types either.

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"},
                                           };

This runs the above code and generates the format you need, albeit without line breaks and indentation.

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

You can use C# XML serialization:

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: blog entry Serialize and deserialize objects as Xml using generic types in C# 2.0 .)

To serialize a Dictionary , I'd suggest to convert it to a list of couples (possibly with LINQ ), because it is not serializable.

Check also Controlling XML Serialization Using Attributes to edit entries' names.

OK, after your clarification, the first, absurdly hard (or not feasible at all) task that comes to my mind is to somehow change programmatically the Type of the elements to reflect the name of the tag using the standard serializer. I don't know if it will work though.

An alternate option would be to use reflection in your custom serializer to get the name of the property or variable as wells as it's value and use that to build the XML. This way you wouldn't need to know what you were passing in or the name of its properties. Be aware that using reflection is slow so if you're serializing a large number of objects this may not be the way to go.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM