Десериализация XML с несколькими типами - PullRequest
0 голосов
/ 23 мая 2018

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

field-text-valueType

field-date-valueType

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

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

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

1 Ответ

0 голосов
/ 25 мая 2018

Атрибуты xsi:type, которые вы видите в своем XML, являются стандартными атрибутами XML-схемы W3C, которые позволяют элементу явно указывать его тип;подробности см. здесь .Как объяснено в Xsi: Поддержка связывания атрибутов типа , 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: сбой атрибута типа 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 скрипки здесь .

...