【问题标题】:IXmlSerializable ReadXml implementationIXmlSerializable ReadXml 实现
【发布时间】:2019-04-30 19:25:12
【问题描述】:

我试图在执行 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 中的参数化构造函数传递标签的名称并在那里设置名称属性,但是在执行反序列化时,它使用默认构造函数然后设置属性值,这样不是一个选项。

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

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

我愿意以任何方式解决这个问题,但我很确定这需要实现IXmlSerializable.ReadXml()。我知道XmlReader 的工作是阅读整个 XML 并将读者推进到文本的末尾,但我有点不清楚如何做到这一点。

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

编辑:使用更新的ReadXml 方法,我获得了ItemProperty 级别所需的所有数据,但ItemData 类,Items 列表只有一项。我假设是因为我没有正确地推进读者。

【问题讨论】:

  • 可能相关:Xmlserializer to C# object, store original XML element。这是否回答你的问题?它显示了将与IXmlSerializable 节点对应的整个元素加载到XElement 中的示例。
  • 不完全是,因为该示例在运行前就知道被反序列化的元素名称。我确实在ItemProperty 级别获得了我需要的所有数据,但我相信读者并没有进步,因为ItemData.Items 只包含一项。请参阅底部的编辑。谢谢。

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


【解决方案1】:

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

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

您的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.

【讨论】:

    猜你喜欢
    • 2010-10-28
    • 1970-01-01
    • 2010-10-13
    • 1970-01-01
    • 2010-09-21
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    • 1970-01-01
    相关资源
    最近更新 更多