我正在尝试在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类可以吗?如果是这样,我应该在我的类上设置什么属性才能使继承部分工作?
答案 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);
}
}
}