2

I'm trying to do what seems like a simple task, but it's proving to be quite complicated.

.NET's System.Xml.Serialization namespace seems to do a really good job of supporting static XML structure, but not so much dynamic structure.

I'm trying to set up an XML document with elements whose children could have one of many types and in arbitrary order. It is my understanding that you can name lists of child elements with the same name this way:

[Serializable]
[XmlRoot("Parent")]
public class MyElement
{
    [XmlElement("Child")]
    public string[] MyChildren { get; set; }
}

This will result in XML that looks like this:

<Parent>
    <Child></Child>
    ...
    <Child></Child>
</Parent>

I'm trying to have a structure that looks like this:

<Parent>
    <ChildTypeA></ChildTypeA>
    <ChildTypeB></ChildTypeB>
    ...
    <ChildTypeZ></ChildTypeZ>
</Parent>

where there is no particular order to them, and types can appear more than once. I've seen some answers where people suggest to use the XmlType attribute on classes to declare the element name, but it seems that the functionality changed between then and now, because all that type does is declare the schema type of the element:

[Serializable]
[XmlRoot("Parent")]
public class MyElement
{
    [XmlElement]
    public BaseChildElement[] MyChildren { get; set; }
}

[Serializable]
public abstract class BaseChildElement {}

[Serializable]
[XmlType("ChildTypeA")]
public class ChildElementA : BaseChildElement
{
    [XmlAttribute]
    public string Content { get; set; }
}

[Serializable]
[XmlType("ChildTypeB")]
public class ChildElementB : BaseChildElement
{
    [XmlAttribute]
    public string Content { get; set; }
}

This will produce XML that looks like this:

<Parent>
    <MyChildren xsi:type="ChildTypeA" Content="" />
    <MyChildren xsi:type="ChildTypeB" Content="" />
    <MyChildren xsi:type="ChildTypeA" Content="" />
    ...
</Parent>

Does someone know how to produce a dynamic list of child elements where the class of the child element gets to set the element name?

HTML is a perfect example of what I'm trying to do. In HTML you can have child elements of arbitrary types in arbitrary order:

<html>
    <head></head>
    <body>
        <p>
            <a href="google.com">Google</a>
            <span>Some text</span>
        <p>
        <div>
            <button>Click me</button>
            <a href="stackoverflow.com">Stack Overflow</a>
        <div>
        <p>Hello world</p>
    </body>
</html>
jchitel
  • 2,999
  • 4
  • 36
  • 49
  • Have you tried `Reflection` to create the class at `runtime`? – dcg Mar 01 '17 at 18:17
  • Take a look at [this question](http://stackoverflow.com/questions/3862226/how-to-create-dynamically-a-class-in-c). The answer given there shows how to create a `type` at runtime. – dcg Mar 01 '17 at 18:23
  • That's not what I'm trying to do. All of the possible child types are statically defined. My issue is that I need a list of children that could be any one of my statically defined types, so I can't set the element name in the parent class, the element name needs to be defined in each child class. – jchitel Mar 01 '17 at 18:29
  • 1
    Related? [Rename class when serializing to XML](http://stackoverflow.com/a/36804496/3744182). – dbc Mar 01 '17 at 18:30

1 Answers1

5

In general, when serializing polymorphic objects (or collections containing polymorphic objects), XmlSerializer requires that the possible types that might be encountered are declared statically in advance, through attributes. Since you want the type to be determined by the element name used rather than by an "xsi:type" attribute, you should apply multiple instances of the [XmlElement(typeof(TBaseChildElement))] attribute to your MyChildren property, one for each possible subtype of BaseChildElement:

[XmlRoot("Parent")]
public class MyElement
{
    [XmlElement(typeof(ChildElementA))]
    [XmlElement(typeof(ChildElementB))]
    public BaseChildElement[] MyChildren { get; set; }
}

public abstract class BaseChildElement { }

[XmlType("ChildTypeA")]
public class ChildElementA : BaseChildElement
{
    [XmlAttribute]
    public string Content { get; set; }
}

[XmlType("ChildTypeB")]
public class ChildElementB : BaseChildElement
{
    [XmlAttribute]
    public string BContent { get; set; }
}

Sample fiddle.

Since XmlElementAttribute.ElementName is not explicitly specified for any of the child types, it will be taken from the XmlTypeAttribute.TypeName of each child type.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • This can work, but I'd obviously prefer if it could use the runtime type of each child to determine the element name, because I have several child types that can be used. Do you know how easy it would be to override XmlSerializer to support what I'm trying to do? – jchitel Mar 01 '17 at 18:45
  • @jchitel - That's what it's doing. As long as you don't specify the `XmlElementAttribute.ElementName` the element name will come from the child type. Or do I misunderstand your question? – dbc Mar 01 '17 at 18:47
  • I was talking about overriding `XmlSerializer` to add the desired behavior. If it's trivial I'd like to give it a shot, otherwise this solution works fine for me. Edit: By "desired behavior" I mean having it use the runtime type *without* having to specify each possible child. – jchitel Mar 01 '17 at 18:49
  • @jchitel - I'm still not entirely clear how the above doesn't give the desired behavior. Is it that you just don't want to have to add `[XmlElement(typeof(TBaseChildElement))]` for all possible derived types? Because otherwise it's working exactly as desired: types can appear in any order in the XML, possibly interleaved, with the name taken from the `[XmlType]` declaration. – dbc Mar 01 '17 at 18:52
  • Yes, sorry I realized I wasn't clear. I added an edit to my previous comment before your reply. – jchitel Mar 01 '17 at 18:53
  • @jchitel - no, it's not easy. You would need to iterate through your assembly and find all derived types of `BaseChildElement` as in [this answer](http://stackoverflow.com/a/27392367/3744182), and add `XmlAttributeOverrides` to specify additional `[XmlElement]` attributes for the `MyChildren` property. Implementing `IXmlSerializable` won't work because you don't want an outer container element. See also [this answer](http://stackoverflow.com/a/28473000/3744182) which is similar but simpler since the presence of an outer element enables use of `IXmlSerializable`. – dbc Mar 01 '17 at 19:01
  • Fair enough, your solution works fine for me then, just a mild inconvenience. Thanks! – jchitel Mar 01 '17 at 19:11