IXmlSerializable ReadXml实现

时间:2018-11-28 19:48:34

标签: c# .net deserialization xml-deserialization ixmlserializable

我试图在执行XML反序列化时将XML标记的名称添加到类属性中。我需要将该名称作为属性,因为多个XML标记共享同一个类。 XML和关联的类在下面定义。

我收到一个XML响应,格式为:

<Data totalExecutionTime="00:00:00.0467241">
    <ItemNumber id="1234" order="0" createdDate="2017-03-24T12:07:09.07" modifiedDate="2018-08-29T16:59:19.127">
        <Value modifiedDate="2017-03-24T12:07:12.77">ABC1234</Value>
        <Category id="5432" parentID="9876" itemOrder="0" modifiedDate="2017-03-24T12:16:23.687">The best category</Category>
        ... <!-- like 100 other elements -->
    </ItemNumber>
</Data>

反序列化的操作如下:

XmlSerializer serializer = new XmlSerializer(typeof(ItemData));
using (TextReader reader = new StringReader(response))
{
    ItemData itemData = (ItemData)serializer.Deserialize(reader);
}

还有一个顶级课程ItemData

[Serializable]
[XmlRoot("Data")]
public class ItemData
{
    [XmlAttribute("totalExecutionTime")]
    public string ExecutionTime { get; set; }

    [XmlElement("ItemNumber", Type = typeof(ItemBase))]
    public List<ItemBase> Items { get; set; }
}

ItemBase定义为:

[Serializable]
public class ItemBase
{
    [XmlElement("Value")]
    public virtual ItemProperty ItemNumber { get; set; } = ItemProperty.Empty;

    [XmlElement("ItemName")]
    public virtual ItemProperty Category { get; set; } = ItemProperty.Empty;

    ... // like 100 other properties
}

最后是ItemProperty

public class ItemProperty : IXmlSerializable
{
    public static ItemProperty Empty { get; } = new ItemProperty();

    public ItemProperty()
    {
        this.Name = string.Empty;
        this.Value = string.Empty;
        this.Id = 0;
        this.Order = 0;
    }

    public string Name { get; set; }

    [XmlText] // no effect while using IXmlSerializable
    public string Value { get; set; }

    [XmlAttribute("id")] // no effect while using IXmlSerializable
    public int Id { get; set; }

    [XmlAttribute("itemOrder")] // no effect while using IXmlSerializable
    public int Order { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();

        string name = reader.Name;
        this.Name = name;

        string val = reader.ReadElementString();
        this.Value = val;

        if (reader.HasAttributes)
        {
            string id = reader.GetAttribute("id");
            this.Id = Convert.ToInt32(id);

            string itemOrder = reader.GetAttribute("itemOrder");
            this.Order = Convert.ToInt32(itemOrder);

            string sequence = reader.GetAttribute("seq");
            this.Sequence = Convert.ToInt32(sequence);
        }

        // it seems the reader doesn't advance to the next element after reading
        if (reader.NodeType == XmlNodeType.EndElement && !reader.IsEmptyElement)
        {
            reader.Read();
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        throw new NotImplementedException();
    }
}

实现IXmlSerializable接口的关键是因为最终我需要存储为ItemProperty的XML标签的名称,并且在使用XML类/属性属性时不会捕获信息。我相信是这种情况,因为属性决定了反序列化所使用的类,并且通常每个XML标记都具有一个关联的类。我不想朝这个方向发展,因为响应中可能包含大量不同的标签,并且它们都共享相似的属性。因此是ItemProperty类。

我也尝试过通过ItemProperty中的参数化构造函数传递标记的名称,并在此处设置name属性,但是执行反序列化时,它将使用默认构造函数,然后设置属性值,所以这不是一个选择。

反射也不起作用,因为类始终为ItemProperty,因此没有唯一的名称。

也许我构造XML属性的方式可能有所不同,以实现我想做的事情,但我看不到。

我愿意以任何方式解决此问题,但是我敢肯定,它需要实现IXmlSerializable.ReadXml()。我知道XmlReader的工作是读取XML的全部内容并将读者推进到文本的结尾,但是我不清楚如何做到这一点。

TLDR:如何在将XML标记名称,标记值和所有属性捕获到类属性中的同时正确实现IXmlSerializable.ReadXml()

编辑:使用更新的ReadXml方法,我获得了ItemProperty级别所需的所有数据,但是ItemData类的Items列表仅包含一项。我想是因为我没有适当地推进读者。

1 个答案:

答案 0 :(得分:0)

摘自IXmlSerializable.ReadXml(XmlReader)的文档:

  

调用此方法时,阅读器位于包装了您的类型信息的开始标签上。 ...当此方法返回时,它必须从头到尾读取整个元素,包括其所有内容。与WriteXml方法不同,该框架不会自动处理wrapper元素。您的实现必须这样做。不遵守这些定位规则可能会导致代码生成意外的运行时异常或损坏的数据。

可以修改您的ReadXml()以满足以下要求:

public void ReadXml(XmlReader reader)
{
    reader.MoveToContent();

    this.Name = reader.LocalName; // Do not include the prefix (if present) in the Name.

    if (reader.HasAttributes)
    {
        var id = reader.GetAttribute("id");
        if (id != null)
            // Since id is missing from some elements you might want to make it nullable
            this.Id = XmlConvert.ToInt32(id);

        var order = reader.GetAttribute("itemOrder");
        if (order != null)
            // Since itemOrder is missing from some elements you might want to make it nullable
            this.Order = XmlConvert.ToInt32(order);

        string sequence = reader.GetAttribute("seq");
        //There is no Sequence property?
        //this.Sequence = Convert.ToInt32(sequence);
    }

    // Read element value.
    // This method reads the start tag, the contents of the element, and moves the reader past the end element tag.
    // thus there is no need for an additional Read()
    this.Value = reader.ReadElementContentAsString();
}

注意:

  1. 您正在呼叫ReadElementString(),其文档为states

      

    我们建议您使用ReadElementContentAsString()方法来读取文本元素。

    根据建议,我修改了您的ReadXml()以使用此方法。反过来,其文档states

      

    此方法读取开始标签,元素的内容,并将阅读器移到结束元素标签之外。

    因此,此方法应使XmlReader的位置完全符合ReadXml()的要求,以确保读者能够正确前进。

  2. 每个ItemProperty元素的XML属性必须在读取该元素的内容之前进行处理,因为读取内容会使读者超越元素开始及其属性。

  3. 应该使用XmlConvert类中的实用工具来解析XML原语并设置其格式,以便不会错误地定位数字和日期/时间值。

  4. 您可能不想在Name属性中包含名称空间前缀(如果有)。

演示小提琴here