如何根据元素的值将xml反序列化为派生类?

时间:2016-09-18 06:55:24

标签: c# xml-deserialization

例如我有一个xml:

<MyFruit>
    <Fruit>
        <Name>Apple</Name>
        <Size>Big</Size>
    </Fruit>
    <Fruit>
        <Name>Orange</Name>
        <Price>10.00</Price>
    </Fruit>
</MyFruit>

您可能会注意到fruit节点包含不同的元素,这是我的伤害:(

然后我定义了以下类来保存反序列化的对象:

public class MyFruit
{
    public List<Fruit> Fruits { get; set; }
}

public abstract class Fruit
{
    public string Name { get; set; }
}

public class Apple : Fruit
{
    public string Size { get; set; }
}

public class Orange : Fruit
{
    public float Price { get; set; }
}

它没有用。

我也尝试过:

  • [XmlInclude(typeof (Apple))][XmlInclude(typeof (Orange))]属性添加到fruit基类以指定具体的派生类
  • [XmlElement(typeof (Apple))][XmlElement(typeof (Orange))属性添加到Fruits类的MyFruit属性

它们都不起作用。

所以我想知道有没有一种方法可以根据元素的值控制反序列化过程(如果名称为Apple,则反序列化为Apple类,OrangeOrange类......),或者有更好的方法?

更新

我写了一个扩展方法来反序列化xml:

    public static T Deserialize<T>(this string xml)
    {
        if (string.IsNullOrEmpty(xml))
        {
            return default(T);
        }
        try
        {
            var xmlserializer = new XmlSerializer(typeof(T));
            var stringReader = new StringReader(xml);
            using (var reader = XmlReader.Create(stringReader))
            {
                return (T) xmlserializer.Deserialize(reader);
            }
        }
        catch (Exception ex)
        {
            throw new Exception("反序列化发生错误", ex);
        }
    }

4 个答案:

答案 0 :(得分:3)

一种方法是简单地将输入xml通过XslCompiledTransform类转换为可以轻松反序列化为所需对象结构的格式。以下示例演示了该概念:

 // XML Deserialization helper.
class XmlSerializationHelper
{

    // Transform the input xml to the desired format needed for de-serialization.
    private static string TransformXml(string xmlString)
    {
        // XSL transformation script.
        string xsl = @"<xsl:stylesheet xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"" version=""1.0"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
                      <xsl:template match=""MyFruit"">
                          <xsl:element name=""{local-name()}"">
                            <Fruits>
                              <xsl:for-each select=""Fruit"">
                                  <xsl:element name=""Fruit"">
                                     <xsl:attribute name=""xsi:type""><xsl:value-of select=""Name""/></xsl:attribute>
                                     <xsl:copy-of select=""./node()""/>
                                  </xsl:element>
                               </xsl:for-each>
                            </Fruits>
                           </xsl:element>
                        </xsl:template>
                    </xsl:stylesheet>";

        // Load input xml as XmlDocument
        XmlDocument sourceXml = new XmlDocument();
        sourceXml.LoadXml(xmlString);

        // Create XSL transformation.
        XslCompiledTransform transform = new XslCompiledTransform();
        transform.Load(new XmlTextReader(new StringReader(xsl)));

        // Apply transformation to input xml and write result out to target xml doc.
        XmlDocument targetXml = new XmlDocument(sourceXml.CreateNavigator().NameTable);
        using (XmlWriter writer = targetXml.CreateNavigator().AppendChild())
        {
            transform.Transform(sourceXml, writer);
        }

        // Return transformed xml string.
        return targetXml.InnerXml;
    }

    public static T DeSerialize<T>(string inputXml)
    {
        T instance = default(T);
        if (string.IsNullOrEmpty(inputXml))
            return instance;

        try
        {
            string xml = TransformXml(inputXml);  // Transform the input xml to the desired xml format needed to de-serialize objects.

            string attributeXml = string.Empty;
            using (StringReader reader = new StringReader(xml))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                using (XmlReader xmlReader = new XmlTextReader(reader))
                {
                    instance = (T)serializer.Deserialize(xmlReader);
                    xmlReader.Close();
                }
                reader.Close();
            }
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }

        return instance;
    }
}

现在可以使用辅助类:

        string inputXml = @"<MyFruit>
                                <Fruit>
                                    <Name>Apple</Name>
                                    <Size>Big</Size>
                                </Fruit>
                                <Fruit>
                                    <Name>Orange</Name>
                                    <Price>10.00</Price>
                                </Fruit>
                            </MyFruit>";

        MyFruit fruits = XmlSerializationHelper.DeSerialize<MyFruit>(inputXml);

答案 1 :(得分:0)

您应该声明Fruit标签的type属性。要查看它,您可以编写序列化方法来查看您获得的xml。例如

public class MyFruit
{
    public List<Fruit> Fruits { get; set; }
}

[XmlInclude(typeof(Apple))]
[XmlInclude(typeof(Orange))]
public abstract class Fruit
{
    public string Name { get; set; }
}

public class Apple : Fruit
{
    public string Size { get; set; }
}

public class Orange : Fruit
{
    public float Price { get; set; }
}

public class Program
{
    static void Main(string[] args)
    {
        MyFruit myFruit = new MyFruit()
        {
            Fruits = new List<Fruit>()
            {
                new Apple() {Name = "Apple", Size = "Big"},
                new Orange() {Name = "Orange", Price = 10.00f}
            }
        };

        string xml;
        XmlSerializer xsSubmit = new XmlSerializer(typeof(MyFruit));
        StringWriter sww = new StringWriter();
        using (XmlTextWriter writer = new XmlTextWriter(sww))
        {
            writer.Formatting=Formatting.Indented;
            xsSubmit.Serialize(writer, myFruit);
            xml = sww.ToString();
            Console.WriteLine(xml);
        }
    }
}

您将获得以下xml

<?xml version="1.0" encoding="utf-16"?>
<MyFruit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Fruits>
    <Fruit xsi:type="Apple">
      <Name>Apple</Name>
      <Size>Big</Size>
    </Fruit>
    <Fruit xsi:type="Orange">
      <Name>Orange</Name>
      <Price>10</Price>
    </Fruit>
  </Fruits>
</MyFruit>

然后你可以使用

var serializer = new XmlSerializer(typeof(MyFruit));

using (var reader = new StringReader(xml))
{
    var res = (MyFruit) serializer.Deserialize(reader);
}

答案 2 :(得分:0)

您可以使用循环和XmlParser。打开&#39;名称&#39;用于解析类特定标记的属性。例如:

Fruit fruit;
List<Fruit> fruits;
while(true)
{
  // ...
  xmlReader.Read();
  xmlReader.ReadStartElement("Fruit");

  xmlReader.ReadStartElement("Name");
  name = xmlReader.ReadString();
  xmlReader.ReadEndElement();

  switch(name)
  {
   case "apple":
    fruit = new Apple();
    try
    {
     xmlReader.ReadStartElement("weight");
     (fruit as Apple).weight = Integer.Parse(xmlReader.ReadString());
     xmlReader.ReadEndElement();
    }catch(){}
    //.....
    break;

   case "orange":
    fruit = new Orange;
    try
    {
     xmlReader.ReadStartElement("color");
     (fruit as Orange).color = xmlReader.ReadString();
     xmlReader.ReadEndElement();
    }catch(){}
    //.....
    break;
   }

   xmlReader.ReadEndElement();
   // ...
 }

答案 3 :(得分:0)

这个会起作用:

[XmlRoot("MyFruit")]
public class MyFruit : List<Fruit>
{
}

public class Fruit
{
    public string Name { get; set; }
    public string Size { get; set; }
    public float Price { get; set; }
}

使用来自@WAKU的反序列化