如何反序列化xml,其中继承的类类型由子元素确定

时间:2016-05-03 13:17:03

标签: c# xml serialization

我正在尝试在C#中反序列化XML文档。 XML文档来自Web API,并且无法更改结构。该文档包含项目列表,每个项目可以是四种类型之一。每个项目的类型在类的子元素中定义,类似于此(为简单起见,键入名称):

glEnableVertexAttribArray

我想将它反序列化为一组类,这些类继承一个名为Item的抽象类,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<items>
    <item>
        <type>Car</type>
        <make>Ford</make>
        <registration>AB00 AAA</registration>
    </item>
    <item>
        <type>Bicycle</type>
        <make>Specialized</make>
        <frameSerialNo>123456768</frameSerialNo>
    </item>
</items>

使用System.Xml.Serialization.XmlSerializer类可以吗?如果是这样,我应该在我的类上设置什么属性才能使继承部分工作?

2 个答案:

答案 0 :(得分:2)

不直接,没有。

您可以使用XmlDocument,XmlReader等手动解析所有数据,也可以将修改后的XML版本提供给XmlSerializer。

XmlSerializer需要xsi:type属性才能直接反序列化该XML。在你的情况下,这将是这样的:

<Item xsi:type="Car">

而不是

<Item>
     <Type>Car</Type>
</Item>

如果您可以在反序列化之前转换该结构(例如,通过操作XmlDocument然后将XmlReader传递给XmlSerializer而不是原始流。

示例:

public static ItemList Load(Stream stream)
{
    XmlDocument document = new XmlDocument();
    document.Load(stream);
    ModifyTypes(document);
    XmlReader reader = new XmlNodeReader(document);
    XmlSerializer serializer = new XmlSerializer(typeof(ItemList));
    return serializer.Deserialize(reader) as ItemList;
}

public static ModifyTypes(XmlDocument document)
{
    const string xsiNamespaceUri = "http://www.w3.org/2001/XMLSchema-instance";

    XmlNodeList nodes = originalDocument.SelectNodes("//Item");
    if (nodes == null) return;

    foreach (XmlNode item in nodes)
    {
        if (item == null) continue;
        if (item.Attributes == null) continue;

        var typeAttribute = item.Attributes["type", xsiNamespaceUri];
        if (typeAttribute != null) continue;

        // here you'll have to add some logic to get the actual 
        // type name based on your structure
        XmlAttribute attribute = document.CreateAttribute("xsi", "type", xsiNamespaceUri);
        attribute.Value = "Car";
        signDefinition.Attributes.Append(attribute);
    }
}

转换数据后,您有两个选择:

1。)为每个继承的类添加一个XmlInclude属性

[XmlInclude(typeof(Bicycle))]
[XmlInclude(typeof(Car))]
abstract class Item

2.。)序列化时明确指定所有继承的类型

XmlSerializer serializer = new XmlSerializer(typeof(ItemList), new[]{
    typeof(Bicycle),
    typeof(Car)   
});

您将面临的另一个问题是,您的数据结构与XML略有不同。

class ItemList
{
    public Item[] Items { get; set; }
}

序列化此ItemList通常会产生类似于此的结构:

<?xml version="1.0"?>
<ItemList xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Items>
        <Item>...</Item>
        <Item>...</Item>
        <Item>...</Item>
    </Items>
</ItemList>

所以你可能想要考虑反序列化:

class ItemList
{
    [XmlArray("Items")]
    [XmlArrayItem("Item")]
    public Item[] Items { get; set; }

    public void Load(Stream stream)
    {
        //Insert Code options from above here
        Items = serializer.Deserializer(typeof(Item[])) as Item[];
    }
}

答案 1 :(得分:0)

这不能直接使用XmlSerializer完成。

但是,有几种方法可以做到。

例如,您可以创建类的实例并手动填充其属性。这样可以保留类的结构。只有ItemList类中的数组才会更改为List以便于添加。

public class ItemList
{
    public List<Item> Items { get; set; }
}


ItemList list = new ItemList();
list.Items = new List<Item>();

using (var reader = XmlReader.Create("test.xml"))
{
    while (reader.ReadToFollowing("item"))
    {
        var inner = reader.ReadSubtree();
        var item = XElement.Load(inner);
        var type = item.Element("type");

        if (type.Value == "Car")
        {
            var car = new Car();
            car.Make = item.Element("make").Value;
            car.Registration = item.Element("registration").Value;
            list.Items.Add(car);
        }
        else if (type.Value == "Bicycle")
        {
            var bicycle = new Bicycle();
            bicycle.Make = item.Element("make").Value;
            bicycle.FrameSerialNumber = item.Element("frameSerialNo").Value;
            list.Items.Add(bicycle);
        }
    }
}

但是,如果分别有许多类属性和XML节点,手动编写大量代码是非常繁琐的。

在这种情况下,您可以单独反序列化每个类。但是,有必要在我们的类中添加XML属性。

[XmlRoot("item")]
public abstract class Item
{
    [XmlElement("make")]
    public string Make { get; set; }
}

[XmlRoot("item")]
public class Bicycle : Item
{
    [XmlElement("frameSerialNo")]
    public string FrameSerialNumber { get; set; }
}

[XmlRoot("item")]
public class Car : Item
{
    [XmlElement("registration")]
    public string Registration { get; set; }
}

public class ItemList
{
    public List<Item> Items { get; set; }
}


ItemList list = new ItemList();
list.Items = new List<Item>();

var carSerializer = new XmlSerializer(typeof(Car));
var bicycleSerializer = new XmlSerializer(typeof(Bicycle));

using (var reader = XmlReader.Create("test.xml"))
{
    while (reader.ReadToFollowing("item"))
    {
        var inner = reader.ReadSubtree();
        var item = XElement.Load(inner);
        var type = item.Element("type");

        if (type.Value == "Car")
        {
            var car = (Car)carSerializer.Deserialize(item.CreateReader());
            list.Items.Add(car);
        }
        else if (type.Value == "Bicycle")
        {
            var bicycle = (Bicycle)bicycleSerializer.Deserialize(item.CreateReader());
            list.Items.Add(bicycle);
        }
    }
}