无法从 xml 反序列化多态列表
Unable to deserialize polymorphic list from xml
我使用 XmlSerializer 从 xml 文件反序列化,而不是 类 由 Xsd2Code 从 xsd 文件生成的 xsd 文件进行反序列化,其中的元素扩展了基本元素。
这是一个简化的例子:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="Vehicle" abstract="true">
<xs:sequence>
<xs:element name="Manufacturer" type="xs:string" nillable="false" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Car">
<xs:complexContent>
<xs:extension base="Vehicle">
<xs:sequence>
<xs:element name="Configuration" type="xs:string" nillable="false" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Truck">
<xs:complexContent>
<xs:extension base="Vehicle">
<xs:sequence>
<xs:element name="Load" type="xs:int" nillable="false" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="Garage">
<xs:complexType>
<xs:sequence>
<xs:element name="Vehicles" type="Vehicle" minOccurs="0" maxOccurs="unbounded" nillable="false" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
生成的代码:
public partial class Garage
{
public Garage()
{
Vehicles = new List<Vehicle>();
}
public List<Vehicle> Vehicles { get; set; }
}
[System.Xml.Serialization.XmlIncludeAttribute(typeof(Truck))]
[System.Xml.Serialization.XmlIncludeAttribute(typeof(Car))]
public partial class Vehicle
{
public string Manufacturer { get; set; }
}
public partial class Truck : Vehicle
{
public int Load { get; set; }
}
public partial class Car : Vehicle
{
public string Configuration { get; set; }
}
XML:
<?xml version="1.0" encoding="utf-8" ?>
<Garage>
<Vehicles>
<Vehicle>
<Manufacturer>Honda</Manufacturer>
<Configuration>Sedan</Configuration>
</Vehicle>
<Vehicle>
<Manufacturer>Volvo</Manufacturer>
<Load>40</Load>
</Vehicle>
</Vehicles>
</Garage>
反序列化代码:
var serializer = new XmlSerializer(typeof(Garage));
using (var reader = File.OpenText("Settings.xml"))
{
var garage = (Garage)serializer.Deserialize(reader);
var car = garage.Vehicles[0] as Car;
Console.WriteLine(car.Configuration);
}
我在反序列化行遇到异常 The specified type is abstract: name='Vehicle', namespace='', at <Vehicle xmlns=''>.
。
如果我从 XSD 中的 Vehicle 元素中删除抽象属性,我会得到空引用异常,因为 garage.Vehicles[0]
无法转换为 Car
。
我希望能够反序列化然后转换为 Car
和 Truck
。我怎样才能完成这项工作?
基本问题是您的 XML 与您的 XSD 不匹配。如果您尝试使用 .Net(示例 fiddle)验证 XML,您将看到以下错误:
The element 'Vehicles' is abstract or its type is abstract.
The element 'Vehicles' has invalid child element 'Vehicle'. List of possible elements expected: 'Manufacturer'.
这些错误的含义如下:
The element 'Vehicles' has invalid child element 'Vehicle'. List of possible elements expected: 'Manufacturer'.
这里的问题是您的 XSD 指定 <Vehicles>
列表没有容器元素。相反,它应该只是一组重复的元素,如下所示:
<Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Vehicles>
<Manufacturer>Honda</Manufacturer>
<Configuration>Sedan</Configuration>
</Vehicles>
<Vehicles>
<Manufacturer>Volvo</Manufacturer>
<Load>40</Load>
</Vehicles>
</Garage>
对应的c#class为:
public partial class Garage
{
public Garage()
{
Vehicles = new List<Vehicle>();
}
[XmlElement]
public List<Vehicle> Vehicles { get; set; }
}
要指定一个包含名为 <Vehicle>
的内部元素的外部包装元素,您的 XSD 必须有一个额外的中间元素 ArrayOfVehicle
:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="Vehicle" abstract="true">
<xs:sequence>
<xs:element name="Manufacturer" type="xs:string" nillable="false" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Car">
<xs:complexContent>
<xs:extension base="Vehicle">
<xs:sequence>
<xs:element name="Configuration" type="xs:string" nillable="false" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Truck">
<xs:complexContent>
<xs:extension base="Vehicle">
<xs:sequence>
<xs:element name="Load" type="xs:int" nillable="false" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- BEGIN CHANGES HERE -->
<xs:complexType name="ArrayOfVehicle">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Vehicle" nillable="true" type="Vehicle" />
</xs:sequence>
</xs:complexType>
<xs:element name="Garage">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Vehicles" type="ArrayOfVehicle" />
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- END CHANGES HERE -->
</xs:schema>
在你的问题中,你写了这是一个简化的例子。 XSD 中的额外嵌套级别是否有可能在编写问题时被手动省略?
The element 'Vehicles' is abstract or its type is abstract.
您正在尝试通过使用 XmlIncludeAttribute
指定可能的子类型来序列化多态列表。
执行此操作时,XML 中的每个多态元素都需要使用 w3c 标准属性 xsi:type
(short for {http://www.w3.org/2001/XMLSchema-instance}type
), as is explained in Xsi:type Attribute Binding Support 断言其实际类型。只有这样 XmlSerializer
才会知道将每个元素反序列化为正确的具体类型。因此你的最终 XML 应该是这样的:
<Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Vehicles xsi:type="Car">
<Manufacturer>Honda</Manufacturer>
<Configuration>Sedan</Configuration>
</Vehicles>
<Vehicles xsi:type="Truck">
<Manufacturer>Volvo</Manufacturer>
<Load>40</Load>
</Vehicles>
</Garage>
或者,如果您希望为您的车辆配备外包装元素 collection:
<Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Vehicles>
<Vehicle xsi:type="Car">
<Manufacturer>Honda</Manufacturer>
<Configuration>Sedan</Configuration>
</Vehicle>
<Vehicle xsi:type="Truck">
<Manufacturer>Volvo</Manufacturer>
<Load>40</Load>
</Vehicle>
</Vehicles>
</Garage>
后面的XML可以成功反序列化成你现在的Garage
class没有报错,如图here.
顺便说一句,调试此类问题的一种简单方法是在内存中创建 Garage
class 的实例,填充其车辆列表,然后将其序列化。这样做之后,您会发现与您尝试反序列化的 XML 不一致。
我使用 XmlSerializer 从 xml 文件反序列化,而不是 类 由 Xsd2Code 从 xsd 文件生成的 xsd 文件进行反序列化,其中的元素扩展了基本元素。
这是一个简化的例子:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="Vehicle" abstract="true">
<xs:sequence>
<xs:element name="Manufacturer" type="xs:string" nillable="false" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Car">
<xs:complexContent>
<xs:extension base="Vehicle">
<xs:sequence>
<xs:element name="Configuration" type="xs:string" nillable="false" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Truck">
<xs:complexContent>
<xs:extension base="Vehicle">
<xs:sequence>
<xs:element name="Load" type="xs:int" nillable="false" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="Garage">
<xs:complexType>
<xs:sequence>
<xs:element name="Vehicles" type="Vehicle" minOccurs="0" maxOccurs="unbounded" nillable="false" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
生成的代码:
public partial class Garage
{
public Garage()
{
Vehicles = new List<Vehicle>();
}
public List<Vehicle> Vehicles { get; set; }
}
[System.Xml.Serialization.XmlIncludeAttribute(typeof(Truck))]
[System.Xml.Serialization.XmlIncludeAttribute(typeof(Car))]
public partial class Vehicle
{
public string Manufacturer { get; set; }
}
public partial class Truck : Vehicle
{
public int Load { get; set; }
}
public partial class Car : Vehicle
{
public string Configuration { get; set; }
}
XML:
<?xml version="1.0" encoding="utf-8" ?>
<Garage>
<Vehicles>
<Vehicle>
<Manufacturer>Honda</Manufacturer>
<Configuration>Sedan</Configuration>
</Vehicle>
<Vehicle>
<Manufacturer>Volvo</Manufacturer>
<Load>40</Load>
</Vehicle>
</Vehicles>
</Garage>
反序列化代码:
var serializer = new XmlSerializer(typeof(Garage));
using (var reader = File.OpenText("Settings.xml"))
{
var garage = (Garage)serializer.Deserialize(reader);
var car = garage.Vehicles[0] as Car;
Console.WriteLine(car.Configuration);
}
我在反序列化行遇到异常 The specified type is abstract: name='Vehicle', namespace='', at <Vehicle xmlns=''>.
。
如果我从 XSD 中的 Vehicle 元素中删除抽象属性,我会得到空引用异常,因为 garage.Vehicles[0]
无法转换为 Car
。
我希望能够反序列化然后转换为 Car
和 Truck
。我怎样才能完成这项工作?
基本问题是您的 XML 与您的 XSD 不匹配。如果您尝试使用 .Net(示例 fiddle)验证 XML,您将看到以下错误:
The element 'Vehicles' is abstract or its type is abstract.
The element 'Vehicles' has invalid child element 'Vehicle'. List of possible elements expected: 'Manufacturer'.
这些错误的含义如下:
The element 'Vehicles' has invalid child element 'Vehicle'. List of possible elements expected: 'Manufacturer'.
这里的问题是您的 XSD 指定
<Vehicles>
列表没有容器元素。相反,它应该只是一组重复的元素,如下所示:<Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Vehicles> <Manufacturer>Honda</Manufacturer> <Configuration>Sedan</Configuration> </Vehicles> <Vehicles> <Manufacturer>Volvo</Manufacturer> <Load>40</Load> </Vehicles> </Garage>
对应的c#class为:
public partial class Garage { public Garage() { Vehicles = new List<Vehicle>(); } [XmlElement] public List<Vehicle> Vehicles { get; set; } }
要指定一个包含名为
<Vehicle>
的内部元素的外部包装元素,您的 XSD 必须有一个额外的中间元素ArrayOfVehicle
:<?xml version="1.0" encoding="utf-8"?> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="Vehicle" abstract="true"> <xs:sequence> <xs:element name="Manufacturer" type="xs:string" nillable="false" /> </xs:sequence> </xs:complexType> <xs:complexType name="Car"> <xs:complexContent> <xs:extension base="Vehicle"> <xs:sequence> <xs:element name="Configuration" type="xs:string" nillable="false" /> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="Truck"> <xs:complexContent> <xs:extension base="Vehicle"> <xs:sequence> <xs:element name="Load" type="xs:int" nillable="false" /> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <!-- BEGIN CHANGES HERE --> <xs:complexType name="ArrayOfVehicle"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Vehicle" nillable="true" type="Vehicle" /> </xs:sequence> </xs:complexType> <xs:element name="Garage"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1" name="Vehicles" type="ArrayOfVehicle" /> </xs:sequence> </xs:complexType> </xs:element> <!-- END CHANGES HERE --> </xs:schema>
在你的问题中,你写了这是一个简化的例子。 XSD 中的额外嵌套级别是否有可能在编写问题时被手动省略?
The element 'Vehicles' is abstract or its type is abstract.
您正在尝试通过使用
XmlIncludeAttribute
指定可能的子类型来序列化多态列表。执行此操作时,XML 中的每个多态元素都需要使用 w3c 标准属性
xsi:type
(short for{http://www.w3.org/2001/XMLSchema-instance}type
), as is explained in Xsi:type Attribute Binding Support 断言其实际类型。只有这样XmlSerializer
才会知道将每个元素反序列化为正确的具体类型。因此你的最终 XML 应该是这样的:<Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Vehicles xsi:type="Car"> <Manufacturer>Honda</Manufacturer> <Configuration>Sedan</Configuration> </Vehicles> <Vehicles xsi:type="Truck"> <Manufacturer>Volvo</Manufacturer> <Load>40</Load> </Vehicles> </Garage>
或者,如果您希望为您的车辆配备外包装元素 collection:
<Garage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Vehicles> <Vehicle xsi:type="Car"> <Manufacturer>Honda</Manufacturer> <Configuration>Sedan</Configuration> </Vehicle> <Vehicle xsi:type="Truck"> <Manufacturer>Volvo</Manufacturer> <Load>40</Load> </Vehicle> </Vehicles> </Garage>
后面的XML可以成功反序列化成你现在的
Garage
class没有报错,如图here.
顺便说一句,调试此类问题的一种简单方法是在内存中创建 Garage
class 的实例,填充其车辆列表,然后将其序列化。这样做之后,您会发现与您尝试反序列化的 XML 不一致。