Reputation: 1002
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?
Upvotes: 10
Views: 2180
Reputation: 21213
However, I am only serializing "ItemWrapper" going forward.
Keep the Item
member, but change it to do what you want during serialization/deserialization. This assumes your code does not use it any more; it only exists for serialization:
// "Item MyItem" is what you had in old version.
// This member is not used in new code; exists only for serialization.
public Item MyItem
{
get => null; // Serializes to null; won't appear in the XML output.
set
{
// When deserialize old version, this does what is needed.
if (value != null)
_myNewItem = MakeItemWrapperFromItem(value);
}
}
// The new member.
public ItemWrapper MyNewItem
{
get => _myNewItem;
set => _myNewItem = value;
}
private ItemWrapper _myNewItem;
private ItemWrapper MakeItemWrapperFromItem(Item value)
{
// Your code here.
}
Approach 2
Subclass XMLSerializer
, and control the serialization/deserialization there.
How to: Control Serialization of Derived Classes.
I like the version idea, but unfortunately that wasn't done for "Item".
But is there a version number higher up? Usually versioning is done only on classes that will be a "root" of an XML file.
This makes is easy for your subclass of XMLSerializer to handle different versions.
Upvotes: 0
Reputation: 71565
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.
Upvotes: 2