Search code examples
c#xml-deserialization

How to deserialize xml to derived classes base on element's value?


For example I have an xml:

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

You may notice that the fruit nodes contain different elements, that's my hurt:(

Then I defined following classes in order to hold the deserialized object:

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

It didn't work.

I also tried:

  • Adding [XmlInclude(typeof (Apple))] and [XmlInclude(typeof (Orange))] attributes to the fruit base class to specify the concrete derived classes
  • Adding [XmlElement(typeof (Apple))] and [XmlElement(typeof (Orange)) attributes to the Fruits property of the MyFruit class

Neither of them works.

So I wonder is there a way that can control the deserialization process base on element's value(if the name is Apple, deserialize to Apple class, Orange to Orange class...), or maybe there are some better ways?

UPDATE

I wrote an extension method to deserialize 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);
        }
    }

Solution

  • One approach is simply to transform the input xml via the XslCompiledTransform class into a format that can be easily de-serialized into the desired object structure. The following example demonstrates the concept:

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

    The helper class can now be used as follow:

            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);