当key是Xml节点名时,如何将xml反序列化为Dictionary?

时间:2016-03-04 18:57:21

标签: c# xml xmlserializer

这是xml

<?xml version="1.0"?>
<TransactionLog>
  <RuleViolations>
    <error>
      <message>error1</message>
      <keys>
        <key1>val1</key1>
        <key2>val2</key2>
        <key3>val3</key3>
        <key4>val4</key4>
      </keys>
    </error>
    <error>
      <message>error1</message>
      <keys>
        <key1>val5</key1>
        <key2>val6</key2>
      </keys>
    </error>
    <error>
      <message>error3</message>
      <keys>
        <key2>val7</key2>
        <key3>val8</key3>
        <key4>val9</key4>
      </keys>
    </error>
  </RuleViolations>
</TransactionLog>

我现在拥有的:

[XmlRoot("TransactionLog")]
public class TransactionLogModel
{
    [XmlArray("RuleViolations")]
    [XmlArrayItem("error")]
    public List<KeyValuePair<string,string>> RuleViolations { get; set; }
}

但我们如何序列化<keys>部分?

我能找到的最接近的SO帖子是:Deserialize XML into Dictionary

但我没有使用XDocument

var x = new XmlSerializer(typeof(TransactionLogModel));
var model = (TransactionLogModel)x.Deserialize(new StringReader(log));    

我们如何在XmlSerializer中反序列化此xml?

1 个答案:

答案 0 :(得分:1)

首先,您的数据模型与您的XML不匹配 - TransactionLogkeys之间缺少几个中间类。相反,它应该看起来像:

[XmlRoot("TransactionLog")]
public class TransactionLogModel
{
    [XmlElement("RuleViolations")]
    public List<RuleViolation> RuleViolations { get; set; }
}

public class RuleViolation
{
    public RuleViolation() { this.Errors = new List<Error>(); }

    [XmlElement("error")]
    public List<Error> Errors { get; set; }
}

public class Error
{
    [XmlElement("message")]
    public string Message { get; set; }

    // To be done.
    public List<KeyValuePair<string, string>> Keys { get; set; }
}

接下来,要使用键名作为元素名称序列化List<KeyValuePair<string, string>> Keys,标准解决方案是implement IXmlSerializable在适当的类型上。这有点令人讨厌,但不是很糟糕,因为你的对值是原始类型(字符串)而不是需要嵌套序列化的复杂类型。

例如,您可以使用Serialize Dictionary member to XML elements and data中的XmlKeyTextValueListWrapper

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
}

然后使用它:

public class Error
{
    [XmlElement("message")]
    public string Message { get; set; }

    List<KeyValuePair<string, string>> keys;

    [XmlIgnore]
    public List<KeyValuePair<string, string>> Keys
    {
        get
        {
            // Ensure keys is never null.
            return (keys = keys ?? new List<KeyValuePair<string, string>>());
        }
        set
        {
            keys = value;
        }
    }

    [XmlElement("keys")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XmlKeyTextValueListWrapper<string> XmlKeys
    {
        get
        {
            return new XmlKeyTextValueListWrapper<string>(() => this.Keys);
        }
        set
        {
            value.CopyTo(Keys);
        }
    }
}

顺便说一句,相同的解决方案将使用public Dictionary<string, string> Keys属性,只需确保预先分配字典:

public class Error
{
    [XmlElement("message")]
    public string Message { get; set; }

    Dictionary<string, string> keys;

    [XmlIgnore]
    public Dictionary<string, string> Keys
    {
        get
        {
            // Ensure keys is never null.
            return (keys = keys ?? new Dictionary<string, string>());
        }
        set
        {
            keys = value;
        }
    }

    [XmlElement("keys")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XmlKeyTextValueListWrapper<string> XmlKeys
    {
        get
        {
            return new XmlKeyTextValueListWrapper<string>(() => this.Keys);
        }
        set
        {
            value.CopyTo(Keys);
        }
    }
}