Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
732 views
in Technique[技术] by (71.8m points)

c# - Serialize Dictionary<string,string> member to XML elements and data

I have a class 'products' that is serializable to XML. I'm using the standard System.Xml.Serialization.XmlSerializer to serialize and a XmlWriter 'writer' object to write the serialized results to a StreamWriter object. The serializer object now serializes the whole class in one go:

XmlSerializer serializer = new XmlSerializer(typeof(products));
serializer.Serialize(writer, products);

The class has a Dictionary<string,string> member called 'Specifications'. It is dynamically built, so I don't know the keys beforehand. Here's an example of what data the dictionary may contain (key: value):

  • color: blue
  • length: 110mm
  • width: 55mm

I would like to be able to serialize that property into this:

...
<specifications>
  <color>blue</color>
  <length>110mm</length>
  <width>55mm</width>
</specifications>
...

I know this is poor XML design, but it has to conform to a 3rd party specification.

Is there perhaps a standard attribute that I can use? If not, how would I be able to serialize the dictionary like that?

If you need more code snippets, let me know.

EDIT: Due to some changes in requirement, I let go of the Dictionary<string,string>. Instead, I created a class "Specification":

public class Specification
{
    public string Name;
    public string Value;
    public bool IsOther;

    public Specification() : this(null, null, false) { }

    public Specification(string name, string value) : this(name, value, false) { }

    public Specification(string name, string value, bool isOther)
    {
        Name = name;
        Value = value;
        IsOther = isOther;
    }
}

To avoid repeating the element "spec" by having a List of "Specification" in the product class, I use a plural class "Specifications" that implements the IXmlSerializable interface:

public class Specifications: IXmlSerializable
{
    public List<Specification> Specs = new List<Specification>();

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        //I don't need deserialization, but it would be simple enough now.
        throw new System.NotImplementedException();
    }

    public void WriteXml(XmlWriter writer)
    {
        //write all "standarad", named specs
        //this writes the <color>blue</color>-like elements
        Specs.Where(s => !s.IsOther).ToList().ForEach(s => writer.WriteElementString(s.Name, s.Value));

        //write other specs
        //this writes <other_specs>{name|value[;]}*</other_specs>
        string otherSpecs = string.Join(";", Specs.Where(s => s.IsOther).Select(s => string.Concat(s.Name, "|", s.Value)));
        if (otherSpecs.Length > 0) writer.WriteElementString("other_specs", otherSpecs);
    }
}

The class "Specifications" is applied as:

public class Product
{
    public Product()
    {
        Specifications = new Specifications();
    }

    [XmlElement("specs")]
    public Specifications Specifications;

    //this "feature" will not include <specs/> when there are none
    [XmlIgnore]
    public bool SpecificationsSpecified { get { return Specifications.Specs.Any(); } }

    //...
}

Thank you for providing examples of IXmlSerializable and XmlWriter. I didn't know that interface and usage of XmlWriter - it proved to be a valuable inspiration for me!

*this was my first SO question. What's the most appropriate way to close it? I didn't provide this as my own answer as it is not a real answer to my initial question (about Dictionary).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Assuming that your dictionary value are all simple types that can be converted to a string, you can create your own IXmlSerializable dictionary wrapper to store and retrieve the keys and values:

public class XmlKeyTextValueListWrapper<TValue> : CollectionWrapper<KeyValuePair<string, TValue>>, IXmlSerializable
{
    public XmlKeyTextValueListWrapper() : base(new List<KeyValuePair<string, TValue>>()) { } // For deserialization.

    public XmlKeyTextValueListWrapper(ICollection<KeyValuePair<string, TValue>> baseCollection) : base(baseCollection) { }

    public XmlKeyTextValueListWrapper(Func<ICollection<KeyValuePair<string, TValue>>> getCollection) : base(getCollection) {}

    #region IXmlSerializable Members

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        var converter = TypeDescriptor.GetConverter(typeof(TValue));
        XmlKeyValueListHelper.ReadXml(reader, this, converter);
    }

    public void WriteXml(XmlWriter writer)
    {
        var converter = TypeDescriptor.GetConverter(typeof(TValue));
        XmlKeyValueListHelper.WriteXml(writer, this, converter);
    }

    #endregion
}

public static class XmlKeyValueListHelper
{
    public static void WriteXml<T>(XmlWriter writer, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
    {
        foreach (var pair in collection)
        {
            writer.WriteStartElement(XmlConvert.EncodeName(pair.Key));
            writer.WriteValue(typeConverter.ConvertToInvariantString(pair.Value));
            writer.WriteEndElement();
        }
    }

    public static void ReadXml<T>(XmlReader reader, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
    {
        if (reader.IsEmptyElement)
        {
            reader.Read();
            return;
        }

        reader.ReadStartElement(); // Advance to the first sub element of the list element.
        while (reader.NodeType == XmlNodeType.Element)
        {
            var key = XmlConvert.DecodeName(reader.Name);
            string value;
            if (reader.IsEmptyElement)
            {
                value = string.Empty;
                // Move past the end of item element
                reader.Read();
            }
            else
            {
                // Read content and move past the end of item element
                value = reader.ReadElementContentAsString();
            }
            collection.Add(new KeyValuePair<string,T>(key, (T)typeConverter.ConvertFromInvariantString(value)));
        }
        // Move past the end of the list element
        reader.ReadEndElement();
    }

    public static void CopyTo<TValue>(this XmlKeyTextValueListWrapper<TValue> collection, ICollection<KeyValuePair<string, TValue>> dictionary)
    {
        if (dictionary == null)
            throw new ArgumentNullException("dictionary");
        if (collection == null)
            dictionary.Clear();
        else
        {
            if (collection.IsWrapperFor(dictionary)) // For efficiency
                return;
            var pairs = collection.ToList();
            dictionary.Clear();
            foreach (var item in pairs)
                dictionary.Add(item);
        }
    }
}

public class CollectionWrapper<T> : ICollection<T>
{
    readonly Func<ICollection<T>> getCollection;

    public CollectionWrapper(ICollection<T> baseCollection)
    {
        if (baseCollection == null)
            throw new ArgumentNullException();
        this.getCollection = () => baseCollection;
    }

    public CollectionWrapper(Func<ICollection<T>> getCollection)
    {
        if (getCollection == null)
            throw new ArgumentNullException();
        this.getCollection = getCollection;
    }

    public bool IsWrapperFor(ICollection<T> other)
    {
        if (other == Collection)
            return true;
        var otherWrapper = other as CollectionWrapper<T>;
        return otherWrapper != null && otherWrapper.IsWrapperFor(Collection);
    }

    ICollection<T> Collection { get { return getCollection(); } }

    #region ICollection<T> Members

    public void Add(T item)
    {
        Collection.Add(item);
    }

    public void Clear()
    {
        Collection.Clear();
    }

    public bool Contains(T item)
    {
        return Collection.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        Collection.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return Collection.Count; }
    }

    public bool IsReadOnly
    {
        get { return Collection.IsReadOnly; }
    }

    public bool Remove(T item)
    {
        return Collection.Remove(item);
    }

    #endregion

    #region IEnumerable<T> Members

    public IEnumerator<T> GetEnumerator()
    {
        return Collection.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

And then use it like so:

[XmlRoot("products")]
public class Products
{
    public Products()
    {
        Specifications = new Dictionary<string, string>();
    }

    [XmlIgnore]
    [JsonProperty("specifications")] // For testing purposes, I compare Json.NET serialization before and after XML serialization.  You can remove this.
    public Dictionary<string, string> Specifications { get; set; }

    [XmlElement("specifications")]
    [JsonIgnore] // For testing purposes, I compare Json.NET serialization before and after XML serialization.  You can remove this.
    public XmlKeyTextValueListWrapper<string> XmlSpecifications
    {
        get
        {
            return new XmlKeyTextValueListWrapper<string>(() => this.Specifications);
        }
        set
        {
            value.CopyTo(Specifications = (Specifications ?? new Dictionary<string, string>()));
        }
    }
}

The fact that your dictionary values are simple types (directly convertible from and to text) makes it possible to avoid nested creations of XmlSerializer, which is more complex. See here for an example.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...