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

时间:2016-09-24 23:04:55

标签: c# xsd xmlserializer xsd2code

我使用XmlSerializer从xml文件中反序列化xsd2Code从xsd文件生成的类,其中元素扩展了一个基本元素。

这是一个简化的例子:

context

生成的代码:

<?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>

XML:

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; }
}

反序列化代码:

var serializer = new XmlSerializer(typeof(Garage));

<?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>

我在反序列化行上遇到异常using (var reader = File.OpenText("Settings.xml")) { var garage = (Garage)serializer.Deserialize(reader); var car = garage.Vehicles[0] as Car; Console.WriteLine(car.Configuration); }

如果我从XSD中的Vehicle元素中删除了抽象属性,我会得到一个空引用异常,因为The specified type is abstract: name='Vehicle', namespace='', at <Vehicle xmlns=''>.无法强制转换为garage.Vehicles[0]

我希望能够反序列化,然后转换为CarCar。我怎样才能做到这一点?

1 个答案:

答案 0 :(得分:2)

基本问题是您的XML与您的XSD不匹配。如果您尝试使用.Net验证XML(示例fiddle),您将看到以下错误:

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的缩写)来声明其实际类型,如{{3}中所述}。只有这样,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类中,而不会出现错误,如Xsi:type Attribute Binding Support所示。

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