0

I have XML that looks something like this:

<root>
    <base type="a">
        <common>1</common>
        <concreteA>one</concreteA>
    </base>
    <base type="b">
        <common>2</common>
        <concreteB>two</concreteB>
    </base>
</root>

And classes like this:

public class Root
{
    public List<Base> Bases { get; set; }
}

public class Base
{
    public int Common { get; set; }
}

public class A : Base
{
    public string ConcreteA { get; set; }
}

public class B : Base
{
    public string ConcreteB { get; set; }
}

How can I deserialize this into objects? I've seen many posts on how to do it when each base node has a different name using XmlArrayItemAttribute( ElementName, Type )], but I need to choose it based on the elements type attribute instead.

Community
  • 1
  • 1
Josh Close
  • 22,935
  • 13
  • 92
  • 140

1 Answers1

0

This is a very basic way of doing it, but I think it works. I think if the class type was an element instead of an attribute then you should be able to parse it declaratively.

The code basically switches on the type attribute top determine what subclass to create and then manually populates the concrete properties and the common property.

[System.Xml.Serialization.XmlType("base")]
public class Base
{
    [System.Xml.Serialization.XmlElement("common")]
    public int Common { get; set; }
}

public class A : Base
{
    public string ConcreteA { get; set; }
}

public class B : Base
{
    public string ConcreteB { get; set; }
}

[System.Xml.Serialization.XmlRootAttribute("root")]
public class Root : System.Xml.Serialization.IXmlSerializable
{
    [System.Xml.Serialization.XmlElement("base")]
    public List<Base> Bases { get; set; }

     public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        this.Bases = new List<Base>();
        var document = XDocument.Load(reader);

        foreach (var element in document.Root.Elements())
        {
            Base baseElement = null;

            var attr = element.Attribute("type");

            if(attr.Value == "a")
            {
                var a = new A();
                a.ConcreteA = element.Element("concreteA").Value;
                baseElement = a;
            }
            else
            {
                var b = new B();
                b.ConcreteB = element.Element("concreteB").Value;
                baseElement = b;
            }

            baseElement.Common = int.Parse(element.Element("common").Value);
            this.Bases.Add(baseElement);
        }

        this.Bases.Dump();
    }

    public void WriteXml(XmlWriter writer)
    {
        throw new NotSupportedException();
    }
}

void Main()
{
    var xmlString = @"<root>
    <base type=""a"">
        <common>1</common>
        <concreteA>one</concreteA>
    </base>
    <base type=""b"">
        <common>2</common>
        <concreteB>two</concreteB>
    </base>
</root>";

    var stream = new StringReader(xmlString);
    var deserializer = new System.Xml.Serialization.XmlSerializer(typeof(Root));
    var result = (Root)deserializer.Deserialize(stream);    
}
NeddySpaghetti
  • 13,187
  • 5
  • 32
  • 61
  • Basic works. ;) How would this work with sub objects on one of those classes? Is there an automatic way of doing those, or does everything in the graph have to be manual this way? – Josh Close Jun 07 '13 at 06:41
  • I think they will need to implement `System.Xml.Serialization.IXmlSerializable` and when you read an element of the subobject class name you can create the subobject instance and call `ReadXml` on it. See this [post](http://stackoverflow.com/questions/2441673/reading-xml-with-xmlreader-in-c-sharp) – NeddySpaghetti Jun 07 '13 at 11:34