当xml具有缩进/换行符时,字典的自定义序列化失败

时间:2017-08-23 15:47:38

标签: c# xml serialization ixmlserializable

为了拥有一个更简洁的Dictionary序列化XML,我编写了一个实现IXmlSerializable的自定义类。

我的自定义类定义如下:

public class MyCollection : System.Collections.Generic.Dictionary<string, string>, IXmlSerializable
{
    private const string XmlElementName = "MyData";
    private const string XmlAttributeId = "Id";

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();
        while (reader.Read())
        {
            if(reader.LocalName == XmlElementName)
            {
                var tag = reader.GetAttribute(XmlAttributeId);
                var content = reader.ReadElementContentAsString(); 

                this.Add(tag, content);
            }
        }
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        foreach (string key in this.Keys)
        {
            writer.WriteStartElement(XmlElementName);
            writer.WriteAttributeString(XmlAttributeId, key);
            writer.WriteString(this[key]);
            writer.WriteEndElement();
        }
    }
}

我的代码适用于此XML代码段:

<MyCollection xmlns="http://schemas.datacontract.org/2004/07/MyProject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <MyData Id="1">some content</MyData>
    <MyData Id="2">some other content</MyData>
</MyCollection>

但是,当我有这个缩小的XML时,我的代码会引发异常:

<MyCollection xmlns="http://schemas.datacontract.org/2004/07/MyProject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><MyData Id="1">some content </MyData><MyData Id="2">some other content</MyData></MyCollection>

例外是:

System.InvalidOperationException: The ReadElementContentAsString method is not supported on node type EndElement

调用ReadElementContentAsString

如何修复我的代码?

我可以使用以下方式重现问题:

var xml = @"<MyCollection xmlns=""http://schemas.datacontract.org/2004/07/MyProject"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""><MyData Id=""1"">some content </MyData><MyData Id=""2"">some other content</MyData></MyCollection>";

var raw = Encoding.UTF8.GetBytes(xml);

var serializer = new DataContractSerializer(typeof(MyCollection));

using (var ms = new MemoryStream(raw))
{
    var result = serializer.ReadObject(ms); // Exception throws here
}

1 个答案:

答案 0 :(得分:2)

您的问题是reader.ReadElementContentAsString()将读者置于下一个节点的开头,而不是当前节点的末尾。然后,您随后对reader.Read()的无条件调用将消耗该下一个节点。当该节点是空白时,不会造成任何伤害,但当节点是一个元素时,该元素将被跳过。

以下版本的MyCollection修复了此问题:

public class MyCollection : System.Collections.Generic.Dictionary<string, string>, IXmlSerializable
{
    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        using (var subReader = reader.ReadSubtree())
        {
            XmlKeyValueListHelper.ReadKeyValueXml(subReader, this);
        }
        // Consume the EndElement also (or move past the current element if reader.IsEmptyElement).
        reader.Read();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlKeyValueListHelper.WriteKeyValueXml(writer, this);
    }
}

public static class XmlKeyValueListHelper
{
    private const string XmlElementName = "MyData";
    private const string XmlAttributeId = "Id";

    public static void WriteKeyValueXml(System.Xml.XmlWriter writer, ICollection<KeyValuePair<string, string>> collection)
    {
        foreach (var pair in collection)
        {
            writer.WriteStartElement(XmlElementName);
            writer.WriteAttributeString(XmlAttributeId, pair.Key);
            writer.WriteString(pair.Value);
            writer.WriteEndElement();
        }
    }

    public static void ReadKeyValueXml(System.Xml.XmlReader reader, ICollection<KeyValuePair<string, string>> collection)
    {
        if (reader.IsEmptyElement)
        {
            reader.Read();
            return;
        }

        reader.ReadStartElement(); // Advance to the first sub element of the list element.
        while (reader.NodeType != XmlNodeType.EndElement)
        {
            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == XmlElementName)
            {
                var tag = reader.GetAttribute(XmlAttributeId);
                string content;
                if (reader.IsEmptyElement)
                {
                    content = string.Empty;
                    // Move past the end of item element
                    reader.Read();
                }
                else
                {
                    // Read content and move past the end of item element
                    content = reader.ReadElementContentAsString();
                }
                collection.Add(new KeyValuePair<string, string>(tag, content));
            }
            else
            {
                // For instance a comment.
                reader.Skip();
            }
        }
        // Move past the end of the list element
        reader.ReadEndElement();
    }
}

一些注意事项:

  • 使用XmlReader.ReadSubtree()我确保ReadXml()不会读取MyCollection元素的末尾,从而破坏未来的元素 - 实施时很容易犯错.Net fiddle 1}}。

  • 通过检查IXmlSerializable我忽略意外类型的节点,例如评论。

工作jsfiddle

相关问题