1

I have XML in a format like so:

<TaskList>
<Task type="TaskA"/>
<Task type="TaskB"/>
</TaskList>

that I am trying to deserialize into classes like so:

public class TaskList
{
    [XmlElement(ElementName = "Task", Type = typeof(Task))]
    public Task[] Task { get; set; } 
}

[XmlInclude(typeof(TaskA))]
[XmlInclude(typeof(TaskB))]
[XmlRoot(ElementName = "Task")]
public class Task
{
    //Common Fields
}

public class TaskA : Task
{
    //Specific stuff
}

public class TaskB : Task
{
    //Specific stuff
}

Trying to deserialize it:

TaskList taskList = (TaskList)new XmlSerializer(typeof(TaskList)).Deserialize(new StringReader(xmlString));

In this configuration it'll give me all of the tasks as the base type "Task". How can I get it to use the derived classes?

Shawn
  • 2,356
  • 6
  • 48
  • 82
  • Interesting question. As far as I know, you will not get this functionality out of the box with the XmlSerializer class. You will need to extend the functionality of the XmlSerializer in a custom class in order to instantiate the correct derived type based on the `type` node attribute. Maybe someone else will get to it before I do, but I'll work on a sample implementation. This question is related and may have a useful answer to get you started: http://stackoverflow.com/questions/17236823/can-i-provide-custom-serialization-for-xmlserializer-without-implementing-ixmlse – xDaevax Aug 08 '14 at 15:15
  • The type information is usually persisted within specialized namespace (declaration -xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance") xsi:type="TaskA" xsi:type="TaskB" should do the job. Have you tried opposite approach (serialize few dummy rows and see how the xml will look like ? ) – Ondrej Svejdar Aug 08 '14 at 15:24
  • @OndrejSvejdar I don't understand what you mean? Unfortunately I can't change the XML format coming in. – Shawn Aug 08 '14 at 15:27
  • @xDaevax Thanks for the help. I've never done anything with the XmlSerializer besides its basic usage, so any help is appreciated. – Shawn Aug 08 '14 at 15:28
  • @OndrejSvejdar What flags need to be set on the XmlSerializer instance or on the classes being serialized to cause this behavior when serializing the type? That is fantastic news! – xDaevax Aug 08 '14 at 15:29
  • @xDaevax - Shawn has it already there - it is the XmlInclude, unfortunately, he does not control the input xml. – Ondrej Svejdar Aug 08 '14 at 16:11
  • Hacky as hell, but `xmlString = xmlString.Replace(" type=", " xsi:type=")` is probably the simplest solution for me right now. – Shawn Aug 11 '14 at 16:57

1 Answers1

1

The XmlSerializer would be able to take types into account with your classes if the xml would look like this:

<?xml version="1.0" encoding="utf-16"?>
<TaskList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Task xsi:type="TaskA" />
  <Task xsi:type="TaskB" />
</TaskList>

Notice the type information is persisted within special namespace. However as you can't control xml format, you'll have to implement IXmlSerializable on TaskList and do the dirty work yourself.

Personally as the xml is simple, I would use LinqToXml.

static Task TaskFromElement(XElement element) {
  switch (element.Attribute("type").Value) {
    case "TaskA":
      return new TaskA();
    case "TaskB":
      return new TaskB();
    default:
      throw new NotImplementedException();
  }
}

var doc = XDocument.Parse(@"<TaskList>
  <Task type=""TaskA""/>
  <Task type=""TaskB""/>
</TaskList>");
var tasks = from e in doc.Descendants("Task")
            select TaskFromElement(e);
var taskList = new TaskList { Task = tasks.ToArray() };    
Ondrej Svejdar
  • 21,349
  • 5
  • 54
  • 89
  • Unfortunately I simplified the xml an awful lot for this question (there are additional types under TaskList and everything has attributes) so I don't think LinqToXml is going to work here. – Shawn Aug 11 '14 at 13:44