反序列化具有多种类型的XML

反序列化具有多种类型的XML

问题描述:

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

I'm trying to deserialize XML where some same name tags have different xsi types:

<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反序列化到此模型中来实现:

This is easily achieved by deserializing the xml into this model:

[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中的类型:

The only thing that differs are the types in the XML:

field-text-valueType

field-date-valueType

我如何使用类似的方法使C#类解释两种类型?

How can I make the C# class interpret both types using something like

[XmlType("field-text-valueType")]

反序列化不序列化

在XML中看到的 xsi:type 属性是标准的W3C XML Schema属性,该属性允许元素显式指定其类型.;有关详细信息,请参见此处.如 Xsi​​:type属性绑定支持 所述>, XmlSerializer 支持这种用于多态类型反序列化的机制,特别是通过使用

The xsi:type attributes you are seeing in your XML are standard W3C XML Schema attributes that allow an element to explicitly specify its type; for details see here. As explained in Xsi:type Attribute Binding Support, XmlSerializer supports this mechanism for deserialization of polymorphic types, specifically by use of XmlIncludeAttribute.

首先,如下定义一个抽象基类 FieldValue :

First, define an abstract base class FieldValue as follows:

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 的派生类型,其

Next, for each possible xsi:type="XXX" value, define a derived type of FieldValue whose XmlTypeAttribute.TypeName matches the xsi:type value. Decorate the base class with [XmlInclude(typeof(TDerivedFieldValue))] attributes for each (already shown above):

[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> 和其他更高元素相对应的包含类型,如下所示:

Then define the containing type corresponding to <field> and other, higher elements as follows:

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

注意:

  • 如果基类 FieldValue 具有通过一旦定义了 [XmlType] 命名空间,它将自动应用于所有序列化的属性,因此无需通过 [XmlElement(Namespace ="http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0)] 属性.

    Once an [XmlType] namespace is defined, it automatically applies to all serialized properties, so it isn't necessary to specify the same namespace via [XmlElement(Namespace = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0")] attributes.

    我厌倦了重复输入名称空间"http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0" ,因此我将其提取为一个常量.

    I got tired of repeatedly typing the namespace "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0" and so I extracted it into a constant.

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

    Other derived types of FieldType can be added easily, e.g.:

    [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))] .

    Don't forget to add [XmlInclude(typeof(DecimalFieldValue))] when doing so.

    如果您已获得XML的XSD,则尝试通过反序列化来指定< field-value> 的可能类型,例如.一个 < xsd:extension> 元素如 根据XML模式生成XML文档所示:抽象类型 ,然后 xsd.exe 将生成包含适当类型层次结构的类.但是,如果只有XML,则 xsd.exe

    If you have been given an XSD for the XML you are trying to deserialize that specifies the possible types of <field-value> via e.g. an <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 will not generate a correct type hierarchy using whatever xsi:type attributes are present.

    有关此限制的更多信息,请参见 xsi:type属性弄乱了C#XML反序列化 .

    For more about this limitation see xsi:type attribute messing up C# XML deserialization.

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

    Your XML is not well-formed because it omits a declaration for the xsi: namespace. Also, a default namespace xmlns="http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0" is not defined so none of the elements are actually in this namespace. Thus I assume your XML is a fragment of some larger document that is valid, e.g.

    <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小提琴示例工作此处.

    Sample working .Net fiddle here.