无法从 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

我希望能够反序列化然后转换为 CarTruck。我怎样才能完成这项工作?

基本问题是您的 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可以成功反序列化成你现在的Garageclass没有报错,如图here.

顺便说一句,调试此类问题的一种简单方法是在内存中创建 Garage class 的实例,填充其车辆列表,然后将其序列化。这样做之后,您会发现与您尝试反序列化的 XML 不一致。