无法从xml反序列化多态列表

本文关键字:多态 列表 反序列化 xml | 更新日期: 2023-09-27 18:08:51

我使用XmlSerializer对Xsd2Code从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

我希望能够反序列化,然后转换成CarTruck。我该怎么做呢?

无法从xml反序列化多态列表

基本问题是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#类是:

    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 ({http://www.w3.org/2001/XMLSchema-instance}type的简称)断言其实际类型,如Xsi:类型属性绑定支持中所解释的那样。只有这样,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>
    

    或者,如果您希望为您的车辆集合使用外部包装元素:

    <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类中而不会出现错误,如下所示。

顺便提一下,调试这类问题的一种简单方法是在内存中创建Garage类的实例,填充其车辆列表,并对其进行序列化。这样做之后,您将看到与您试图反序列化的XML不一致。