Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# XML serialization backwards compatibility

Previously, the serialization/deserialization methods used the type Item:

public class Item{}

Now I have a new class called ItemWrapper derived from Item with an additional property:

public class ItemWrapper : Item
{
    public string NewProperty { get; set; }
}

Now my serialization/deserialization methods use the type ItemWrapper. And now I've broken backwards compatibility. I cannot load any XML files of type Item that got saved in older versions. I've considered putting a try/catch on the deserialization method when it tries to deserialize Item as ItemWrapper and then in the catch I would then try to deserialize as Item. Or I could xPath to see the XML structure and if no ItemWrapper is found, I could assume it as Item. Both of these solutions feel hacky, and I'm sure there's a better approach for handling this situation. Any ideas?

like image 517
jsirr13 Avatar asked Aug 14 '14 16:08

jsirr13


1 Answers

Good question. First off, while extending the class by derivation is good adherence to the Open/Closed principle, if all consumers of Item now consume ItemWrapper, then you haven't saved much effort implementing a derived class. If ItemWrapper is now the only concretion in use, then merge its new property with Item and be done.

If you have a need to keep both concretions around, then ItemWrapper needs to be decorated with a few attributes to indicate how the XMLSerializer should transform to and from XML strings. Information on XML attributes can be found here.

In particular, I call your attention to XmlTypeAttribute. This decorates the ItemWrapper class itself, and tells XmlSerializer to use a specific namespace and type name instead of auto-generating them based on the class name. You can use this to make ItemWrapper compatible with XML files created by serializing Item, by stating that the ItemWrapper class should create and consume XML serializations tagged as <Item>. However, Item, if it's still around, will fail when attempting to deserialize a file created by serializing ItemWrapper, so this solution isn't forward-compatible, and so previous versions of your software, if you haven't handled serialization errors robustly, will die a fiery death for no apparent reason when given newer files

For this reason, it's usually a good idea to implement some sort of versioning scheme in your serialization. It could be as simple as a public readonly property on your types, which can be marked with the XmlAttribute attribute, telling XmlSerializer to construct the <Item> tag as <Item xmlVersion="1.0.0">. If you'd done this operviously, then ItemWrapper could override that field to return "1.1.0", allowing the XML files to be easily differentiable and so allowing you to check for an incompatible file version with an XmlTextReader, and gracefully return an error if the file was generated by a later version of the software.

like image 180
KeithS Avatar answered Oct 13 '22 00:10

KeithS