将Dictionary <string,string>成员序列化为XML元素和数据

时间:2015-04-22 11:13:16

标签: c# xml serialization dictionary

我有一个可以序列化为XML的类“产品”。我正在使用标准的System.Xml.Serialization.XmlSerializer进行序列化,并使用XmlWriter“writer”对象将序列化结果写入StreamWriter对象。序列化程序对象现在一次性序列化整个类:

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

该类有一个Dictionary&lt; string,string&gt;成员称为“规格”。它是动态构建的,所以我事先不知道密钥。以下是字典可能包含的数据示例(键:值):

  • 颜色:蓝色
  • 长度:110mm
  • 宽度:55mm

我希望能够将该属性序列化为:

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

我知道这是一个糟糕的XML设计,但它必须符合第三方规范。

是否可以使用标准属性?如果没有,我怎么能这样序列化字典?

如果您需要更多代码段,请与我们联系。

修改: 由于需求的一些变化,我放开了Dictionary&lt; string,string&gt;。相反,我创建了一个类“规范”:

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

为避免在产品类中使用List“List”重复元素“spec”,我使用了一个实现IXmlSerializable接口的复数类“Specifications”:

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

“规格”类适用于:

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

    //...
}

感谢您提供IXmlSerializable和XmlWriter的示例。我不知道XmlWriter的界面和用法 - 它对我来说是一个有价值的灵感!

*这是我的第一个问题。什么是最合适的关闭方式?我没有提供这个作为我自己的答案,因为它不是我最初的问题(关于词典)的真实答案。

2 个答案:

答案 0 :(得分:1)

假设您的字典值都是可以转换为字符串的简单类型,您可以创建自己的IXmlSerializable字典包装来存储和检索键和值:

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
}

然后像这样使用它:

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

您的字典值是简单类型(可直接从文本转换为文本)这一事实使得可以避免XmlSerializer的嵌套创建,这更复杂。有关示例,请参阅here

答案 1 :(得分:0)

使字典NonSerialized

[XmlRoot("specifications")]
    public class Specifications
    {
         [NonSerialized]
         Dictionary<string, string> dict { get; set; }
         [XmlElement("color")]
         string color {get;set;}
         [XmlElement("length")]
         string length { get; set; }
         [XmlElement("width")]
         string width { get; set; }

         public Specifications()
         {
             dict = new Dictionary<string, string>();
         }
    }