Десериализовать XML с несколькими типами

Я пытаюсь десериализовать 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:

тип значения текста поля

тип поля-даты-значения

Как я могу заставить класс С# интерпретировать оба типа, используя что-то вроде

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

РЕДАКТИРОВАТЬ: десериализация, а не сериализация


person OHMR    schedule 23.05.2018    source источник
comment
Вы имеете в виду десериализацию?   -  person stuartd    schedule 23.05.2018
comment
Атрибут type указывает, что класс наследует базовый класс, где field-value — это имя базового класса, а field-text-valueType — подкласс.   -  person jdweng    schedule 23.05.2018


Ответы (1)


Атрибуты xsi:type, которые вы видите в своем XML, являются стандартными атрибутами XML-схемы W3C, которые позволяют элементу явно указывать свой тип; подробности см. здесь. Как описано в разделе Поддержка связывания атрибутов Xsi:type, XmlSerializer поддерживает этот механизм десериализации полиморфных типов, в частности, с помощью XmlIncludeAttribute.

Сначала определите абстрактный базовый класс FieldValue следующим образом:

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.TypeName соответствует значению xsi:type. Украсьте базовый класс [XmlInclude(typeof(TDerivedFieldValue))] атрибутами для каждого (уже показано выше):

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

Примечания:

  • Если базовый класс FieldValue имеет пространство имен, указанное через XmlTypeAttribute.Namespace, то и производные классы должны это делать, иначе 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))].

  • Если вам был предоставлен XSD для XML, который вы пытаетесь десериализовать, который указывает возможные типы <field-value> через, например. элемент <xsd:extension>, как показано в Создание XML-документов из XML-схем: абстрактные типы, затем xsd.exe создаст классы, которые включают соответствующую иерархию типов. Но если у вас есть только XML, то xsd.exe и Вставить XML как классы не создаст правильную иерархию типов, используя любые xsi:type атрибуты.

    Дополнительные сведения об этом ограничении см. в разделе атрибут xsi:type, нарушающий десериализацию C# XML.

  • Ваш XML имеет неправильный формат, поскольку в нем отсутствует объявление пространства имен xsi:. Кроме того, пространство имен по умолчанию xmlns="http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0" не определено, поэтому ни один из элементов фактически не находится в этом пространстве имен. Таким образом, я предполагаю, что ваш 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 здесь.

person dbc    schedule 25.05.2018