список элементов xml в массив общего базового типа

#c# #inheritance #xml-serialization

#c# #наследование #xml-сериализация

Вопрос:

У меня есть XML-файл с таким списком, как этот:

 <TestFile>
  <string>Foo</string>
  <bool>false</bool>
  <bool>true</bool>
  <string>Bar</string>
</TestFile>
  

Я хочу десериализовать его в массив типа «Значение». Этот тип имеет два подтипа «ValueString» и «ValueBool»:

 [XmlRoot("TestFile")]
public class TestFile
{
    public List<Test> Tests;
}

public class Value
{
}

public class ValueString : Value
{
    [XmlText]
    public string Value;
}

public class ValueBool : Value
{
    [XmlText]
    public bool Value;
}
  

Я не могу понять, как это сделать. Я пробовал XmlIncludeAttributes, но этого недостаточно, потому что имена элементов не совпадают с именами классов. Я пробовал XmlChoiceIdentifier, но найденные мной примеры не компилируются, когда я их адаптирую…

Мне нужно сохранить порядок элементов, поэтому разделение элементов на два списка не сработает. Я также не могу изменить структуру xml, потому что она поступает из внешнего источника. Очевидно, что это всего лишь пример — мои реальные типы более сложные…

Комментарии:

1. Лично я бы рекомендовал написать для этого свой собственный код синтаксического анализа xml, поскольку сериализация xml на самом деле не предназначена для чтения xml.

Ответ №1:

Я, наконец, наткнулся на решение:

 [XmlRoot("TestFile")]
public class TestFile
{
    [XmlElement(ElementName = "string", Type = typeof(ValueString))]
    [XmlElement(ElementName = "bool", Type = typeof(ValueBool))]
    public List<Test> Tests;
}
  

Я думал, что пробовал это раньше… Что ж, по крайней мере, сейчас это работает. В этом примере была еще одна проблема: вы не можете отобразить логическое поле на текст элемента, но в любом случае эта конкретная проблема отсутствует в моем реальном сценарии…

Ответ №2:

Часто мы сталкиваемся с необходимостью преобразовать предоставленный нам XML-файл. Да, было бы здорово, если бы все подписались на одну и ту же структуру, но в компаниях среднего и крупного размера добиться этого может быть сложнее.

Я начал с вашего XML-файла:

 <?xml version="1.0" encoding="utf-8" ?>
<TestFile>
  <string>Foo</string>
  <bool>false</bool>
  <bool>true</bool>
  <string>Bar</string>
</TestFile>
  

Затем создал файл преобразования (это всего лишь пример, и все зависит от ваших собственных предпочтений):

 <?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="@* | node()">
      <xsl:copy>
        <ParameterCollection>
          <xsl:apply-templates select="@* | node()"/>
        </ParameterCollection>
      </xsl:copy>
  </xsl:template>
  <xsl:template match="bool">
    <Parameter type="bool">
      <xsl:apply-templates select="node()"/>
    </Parameter>
  </xsl:template>
  <xsl:template match="string">
    <Parameter type="string">
      <xsl:apply-templates select="node()"/>
    </Parameter>
  </xsl:template>
</xsl:stylesheet>
  

Затем я начал собирать все необходимые классы вместе:

 [XmlRootAttribute("TestFile", IsNullable = false)]
public class TestFile
{
    [XmlArrayAttribute("ParameterCollection")]
    public Parameter[] Parameters;
}

public class Parameter
{
    [XmlAttribute("type")]
    public string ObjectType;

    [XmlText]
    public string ObjectValue;
}
  

Затем примените все (надеюсь, более продуманным образом, чем я сделал):

 class Program
{
    static void Main(string[] args)
    {
        FileInfo xmlFile = new FileInfo(@"ResourcesTestFile.xml");
        FileInfo transformFile = new FileInfo(@"ResourcesTestFileTransform.xslt");
        FileInfo prettyFile = new FileInfo(@"ResourcesPrettyFile.xml");

        if (xmlFile.Exists amp;amp; transformFile.Exists)
        {
            // Perform transform operations.
            XslCompiledTransform trans = new XslCompiledTransform();
            trans.Load(transformFile.FullName);
            trans.Transform(xmlFile.FullName, prettyFile.FullName);
        }

        if (prettyFile.Exists)
        {
            // Deserialize the new information.
            XmlSerializer serializer = new XmlSerializer(typeof(TestFile));
            XDocument doc = XDocument.Load(prettyFile.FullName);
            TestFile o = (TestFile)serializer.Deserialize(doc.CreateReader());

            // Show the results.
            foreach (Parameter p in o.Parameters)
            {
                Console.WriteLine("{0}: {1}", p.ObjectType, p.ObjectValue);
            }
        }

        // Pause for effect.
        Console.ReadKey();
    }
}
  

Надеюсь, это кому-то поможет или, по крайней мере, даст им другой вариант. Обычно, ИМХО, я бы предпочел проанализировать файл или поток, но это только для меня.