使用多种类型反序列化 XML

Deserialize XML with multiple types

我正在尝试反序列化 XML,其中一些相同名称的标签具有不同的 xsi 类型:

<user-defined-data-row>
  <field name="entity">
    <field-value xsi:type="field-text-valueType">
      <value>Test</value>
    </field-value>
  </field>
  <field name="expiry_date">
    <field-value xsi:type="field-date-valueType">
      <value>2001-10-07</value>
    </field-value>
  </field>
</user-defined-data-row>

这很容易通过将 xml 反序列化到这个模型中来实现:

[XmlRoot(ElementName = "field-value", Namespace = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0")]
[XmlType("field-text-valueType")]
public class Fieldvalue
{
    [XmlElement(ElementName = "value", Namespace = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0")]
    public string Value { get; set; }
}

唯一不同的是 XML 中的类型:

字段文本值类型

字段日期值类型

如何使 C# class 使用类似

的方式解释这两种类型
[XmlType("field-text-valueType")]

编辑:反序列化不是序列化

您在 XML 中看到的 xsi:type 属性是标准的 W3C XML 架构属性,允许元素明确指定其类型;有关详细信息,请参阅 here. As explained in Xsi:type Attribute Binding Support, XmlSerializer supports this mechanism for deserialization of polymorphic types, specifically by use of XmlIncludeAttribute

首先定义一个抽象基classFieldValue如下:

public static class XmlNamespaces
{
    public const string Crsoftwareinc = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0";
}

[XmlRoot("field-value", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field-value", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlInclude(typeof(TextFieldValue)), 
XmlInclude(typeof(DateFieldValue))]
public abstract partial class FieldValue
{
    // It's not necessary to have this in the base class but I usually find it convenient.
    public abstract object GetValue();
}

接下来,对于每个可能的 xsi:type="XXX" 值,定义 FieldValue 的派生类型,其 XmlTypeAttribute.TypeNamexsi:type 值匹配。用每个 [XmlInclude(typeof(TDerivedFieldValue))] 属性装饰基础 class(已在上面显示):

[XmlRoot("field-text-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field-text-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
public class TextFieldValue : FieldValue
{
    [XmlElement("value")]
    public string Value { get; set; }

    public override object GetValue() { return Value; }
}

[XmlRoot("field-date-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field-date-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
public class DateFieldValue : FieldValue
{
    [XmlElement("value", DataType = "date")]
    public DateTime Value { get; set; }

    public override object GetValue() { return Value; }
}

然后定义<field>和其他更高层元素对应的包含类型如下:

[XmlRoot("field", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field", Namespace = XmlNamespaces.Crsoftwareinc)]
public class Field
{
    [XmlAttribute("name")]
    public string Name { get; set; }

    [XmlElement("field-value")]
    public FieldValue FieldValue { get; set; }
}

[XmlRoot("user-defined-data-row", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("user-defined-data-row", Namespace = XmlNamespaces.Crsoftwareinc)]
public class UserDefinedDataRow
{
    [XmlElement("field")]
    public List<Field> Fields { get; set; }
}

// The XML for the root object is not shown so this is just a stub
[XmlRoot("root", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("root", Namespace = XmlNamespaces.Crsoftwareinc)]
public class RootObject
{
    [XmlElement("user-defined-data-row")]
    public List<UserDefinedDataRow> Rows { get; set; }
}

备注:

  • 如果基础 class FieldValue 有一个在 via XmlTypeAttribute.Namespace 中指定的命名空间,那么派生的 classes 也必须,否则XmlSerializer.

    会抛出错误

    定义 [XmlType] 命名空间后,它会自动应用于所有序列化属性,因此无需通过 [XmlElement(Namespace = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0")] 属性指定相同的命名空间。

  • 我厌倦了重复输入命名空间 "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0",所以我将它提取为常量。

  • 其他FieldType的派生类型可以轻松添加,例如:

    [XmlRoot("field-decimal-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
    [XmlType("field-decimal-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
    public class DecimalFieldValue : FieldValue
    {
        [XmlElement("value")]
        public decimal Value { get; set; }
    
        public override object GetValue() { return Value; }
    }
    
    [XmlInclude(typeof(DecimalFieldValue))]
    public abstract partial class FieldValue { }
    

    不要忘记添加[XmlInclude(typeof(DecimalFieldValue))]

  • 如果您已获得 XML 的 XSD,您正在尝试反序列化,它指定了 <field-value> 的可能类型,例如<xsd:extension> element as shown in Generating XML Documents from XML Schemas: Abstract Types, then xsd.exe will generate classes that include an appropriate type hierarchy. But if you only have the XML, then xsd.exe and Paste XML as Classes 而不是 使用存在的任何 xsi:type 属性生成正确的类型层次结构。

    有关此限制的更多信息,请参阅

  • 您的 XML 格式不正确,因为它省略了 xsi: 命名空间的声明。此外,未定义默认命名空间 xmlns="http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0",因此 none 的元素实际上位于此命名空间中。因此,我假设您的 XML 是一些有效的较大文档的片段,例如

    <root 
     xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns="http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0">
      <user-defined-data-row>
          <!-- Remainder as shown in the question -->
      </user-defined-data-row>
    </root>
    

示例工作.Net fiddle here.