Click here to Skip to main content
15,886,783 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
Hello folks.

I can really use some help here. What I am attempting to do is to create an XML serializer that can handle a class that has several derived subtypes. I already know the basics of doing this: I wrote this blog entry to help me remember how.

But now, I am diving a little deeper and I've gotten my self stuck. Below is simplified sample of the XML I am working from:
<AssignmentDesk name="test desk"/>
  <Extraction>
    <Find>
		...other instructions...
    </Find>
    <Find>
		...other instructions...
    </Find>
	<Set>
		...other instructions...
	</Set>
  </Extraction>
</AssignmentDesk>

And the classes to represent this look like:
[XmlRoot("AssignmentDesk")]
public class AssignmentDesk
{
    [XmlAttribute("name")]
    public string Name { get; set; }

    [XmlElement("Extraction")]
    public List<Extractor> Extractors { get; set; }

    public static XmlAttributeOverrides GetXmlOverrides()
    {
        var attrs = new XmlAttributes();
        foreach (Type type in
            Assembly.GetAssembly(typeof(Instruction)).GetTypes()
            .Where(myType => myType.IsClass && myType.IsSubclassOf(typeof(Instruction))))
        {
            var attr = new XmlArrayItemAttribute(type);
            attrs.XmlArrayItems.Add(attr);
        }

        var overrides = new XmlAttributeOverrides();
        overrides.Add(typeof(Extractor), "SubInstructions", attrs);

        return overrides;
    }
}

public class Instruction
{
    public List<Instruction> SubInstructions { get; set; }
}

public class Extractor : Instruction
{
}

public class Find : Instruction
{
}

public class Set : Instruction
{
}

Of course all these classes have other attributes and methods. I've stripped them down to the bare minimum here.

Now, the Instruction class is meant represent a variety of different instructions (each represented by a classes derived from Instruction). That is, I want to be able to create additional instructions (like Find, Set, Put, Finangle, or whatever) and have them be recognised by the XML serializer as just more Instructions. To do this, I am using the GetXmlOverrides() method on the AssignmentDesk class to look for all classes derived from Instructions and add them to the XML attribute overrides for the SubInstructions property. I then use the returned overrides with the XML serializer
var serializer = new XmlSerializer(typeof(AssignementDesk), overrides, null, new XmlRootAttribute(root), null);

This is where I am running into problems. No matter what I try, when I deserialize the example XML I get Extractors set to a list containing one Extractor object (that's good). But that Extractor object's SubInstructions list is always empty.
I have tried changing the overrides.Add()
overrides.Add(typeof(AssignementDesk), "Extractors", attrs);
overrides.Add(typeof(AssignementDesk), "Extraction", attrs);
overrides.Add(typeof(Instruction), "SubInstructions", attrs);
etc.

I have tried changing the attribute type from XmlArrayItemAttribute to XmlElementAttribute. To be honest, I can't remember all the combinations I have tried. But none of them have worked.
Does anyone out there have an idea of what I may be doing wrong?

Note: You may be wondering why I don't just hard code the [XmlArrayItem] attributes right above the Extractors property (like I did to the Instruments class in my blog entry). The reason is that I am hoping allow additional instructions (that is, classes derived from Instruction) to be created outside of this class. I don't want to have to hard code each new instruction in AssignmentDesk.

Yes, that is a lot of probably unnecessary work, but this is also a learning exercise for me: If I learn how to do this in my own home project, then perhaps I can, at some point long after I have retired, give some poor unsuspecting future code maintainer a massive migraine at 2 AM on a Saturday (a laudable goal, you must admit).

What I have tried:

Variations on changing the attribute types and member names used to create the XML overrides.
Checking the examples given by Microsoft (none of which seem to be quite what I am doing).
Considered trashing my computer and becoming a male stripper (definitely NOT a good idea)
Posted
Updated 27-Aug-19 7:15am
v2

1 solution

The problem seems to be that you don't have a wrapping element, so the array attributes are ignored.

If you had:
XML
<Extraction>
    <SubInstructions>
        <Find />
        <Set />
    </SubInstructions>
</Extraction>
then your code would work.

Without the wrapping element, it looks like you need to use XmlElement instead:
C#
public static XmlAttributeOverrides GetXmlOverrides()
{
    var attrs = new XmlAttributes();
    
    foreach (Type type in
        Assembly.GetAssembly(typeof(Instruction)).GetTypes()
        .Where(myType => myType.IsClass && myType.IsSubclassOf(typeof(Instruction))))
    {
        var attr = new XmlElementAttribute(type);
        attrs.XmlElements.Add(attr);
    }

    var overrides = new XmlAttributeOverrides();
    overrides.Add(typeof(Instruction), "SubInstructions", attrs);
    return overrides;
}
Then the code works with your current XML.

NB: You also need to add the override to the Instruction type, since that's where the attribute is defined, rather than to the Extractor type. If you wanted the overrides to only apply to the property as defined on the Extractor class, you'd have to make it virtual, add [XmlIgnore] in the Instruction class, and override it in the Extractor class.
 
Share this answer
 
Comments
Maciej Los 27-Aug-19 13:31pm    
5ed!
C Pottinger 28-Aug-19 9:50am    
Thank you, Richard.
I added the XmlElementAttribute override to the overrides object and applied the overrides to the Instruction type, as you suggested, The code is now working perfectly.
And thank you for the explanation of how to apply the override to just the Extractor class. I'm not doing that, in this case, but it is good know that it is an option.

Perfect answer.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900