XML deserialisation - backwards compatability

  • Thread starter Thread starter Dylan Nicholson
  • Start date Start date
D

Dylan Nicholson

Hello,

I have an issue where I've replaced a single class field with a number
of separate fields that allow more fine control. The problem is that
my old files, saved using XmlSerialization, still have this old field
name in them, and I need to be able to read it and set the new fields
appropriately.

E.g. the if the old class

class MyClass
{
public int area;
}

and the new version is now

class MyClass
{
public int width;
public int height;
}

then when executing:

XmlTextReader xmlReader = new XmlTextReader(filename);
XmlSerializer xs = new XmlSerializer(typeof(MyClass));
MyClass obj = (MyClass)xs.Deserialize(xmlReader);

I need to be able to look for the "area" value in old files, and set
width and height appropriately (e.g. to the square root of the area
value).

Is there a straightforward way of doing this?

Note the class has at least 30 other members, and that I'm using a
generic piece of code to do the actual serialization that's not
specific to MyClass, though of course I can put a test in to deal with
MyClass as a special case if necessary.
 
Maybe not straightforward, but: if you add a hidden Area property, and
tell it never to serialize, it should work.
Note I've used C# 3 auto-implemented properties for Width/Height -
just replace with standard properties if you are using C# 2; but don't
use public fields... pretty much "ever".

Marc

using System;
using System.ComponentModel;
using System.IO;
using System.Xml.Serialization;

[Serializable]
public sealed class MyClass
{
public int Width { get; set; }
public int Height { get; set; }

[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
public int Area
{
get { return Width * Height; }
set { Width = Height = (int)Math.Sqrt(value); }
}
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
public bool ShouldSerializeArea() { return false; }

public override string ToString()
{
return string.Format("{0}x{1}", Width, Height);
}
}

static class Program
{
static void Main(string[] args)
{
XmlSerializer xs = new XmlSerializer(typeof(MyClass));
MyClass item = new MyClass { Width = 3, Height = 5 };

StringWriter writer = new StringWriter();
xs.Serialize(writer, item);
string xml = writer.ToString();

Console.WriteLine(xml);

using (StringReader sr = new StringReader(@"<MyClass><Area>25</
Area></MyClass>"))
{
MyClass oldStyle = (MyClass)xs.Deserialize(sr);
Console.WriteLine(oldStyle);
}
using (StringReader sr = new StringReader(@"<MyClass><Width>7</
Width><Height>3</Height></MyClass>"))
{
MyClass newStyle = (MyClass)xs.Deserialize(sr);
Console.WriteLine(newStyle);
}
}
}
 
Maybe not straightforward, but: if you add a hidden Area property, and
tell it never to serialize, it should work.
Note I've used C# 3 auto-implemented properties for Width/Height -
just replace with standard properties if you are using C# 2; but don't
use public fields... pretty much "ever".

Marc
Thanks...I didn't know about the ShouldSerialize...() trick - that
solved the problem of the deprecated field being written to new files.
But ideally I don't want the deprecated field to be public either.
Not a biggie, though.
As for public fields - in this case the class is just a holder for
fairly raw data, where no particular validation is needed.
And the beauty of C# is you can change public fields to properties at
any point and existing code will continue to compile.
 
But ideally I don't want the deprecated field to be public either.

Well, it is pretty well hidden by the two attributes - but ultimately
XmlSerializer works on public properties. If you have .NET 3.0,
another option is DataContractSerializer (shown below), since this can
serialize private members; and a third option is custom serialization
(IXmlSerializable), but this is considerably more work.
As for public fields - in this case the class is just a holder for
fairly raw data, where no particular validation is needed.

Then in VS2008 / C# 3, auto implemented properties are definitely the
way to go.
And the beauty of C# is you can change public fields to properties at
any point and existing code will continue to compile.

That isn't entirely true... first: it forces you to rebuild everything
that references it, and second: there are cases when this simply isn't
true; two obvious examples come to mind (there are others):

* using the field as a "ref" or "out" parameter to a method [can't use
properties as "ref" or "out"]
* if the field represents a mutable struct [which you shouldn't have
anyway], and you are changing nested properties [change gets discarded
with a property]

It will almost certainly break binary serialization, too.

Marc

## DataContract example ##

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

[DataContract(Namespace="")]
public sealed class MyClass
{
[DataMember(IsRequired = false)]
public int Width { get; set; }
[DataMember(IsRequired=false)]
public int Height { get; set; }

[DataMember(Name="Area", IsRequired=false,
EmitDefaultValue=false)]
private int? AreaFacade
{
get { return null; }
set {
if (value.HasValue)
{
Width = Height = (int)Math.Sqrt(value.Value);
}
}
}

public override string ToString()
{
return string.Format("{0}x{1}", Width, Height);
}
}

static class Program
{
static void Main(string[] args)
{
DataContractSerializer xs = new
DataContractSerializer(typeof(MyClass));
MyClass item = new MyClass { Width = 3, Height = 5 };

StringWriter writer = new StringWriter();
using (XmlWriter xw = XmlWriter.Create(writer))
{
xs.WriteObject(xw, item);
xw.Close();
}
string xml = writer.ToString();

Console.WriteLine(xml);

using (XmlReader xr = XmlReader.Create(new
StringReader(@"<MyClass><Area>25</Area></MyClass>")))
{
MyClass oldStyle = (MyClass)xs.ReadObject(xr);
Console.WriteLine(oldStyle);
}
using (XmlReader xr = XmlReader.Create(new
StringReader(@"<MyClass><Height>3</Height><Width>7</Width></
MyClass>")))
{
MyClass newStyle = (MyClass)xs.ReadObject(xr);
Console.WriteLine(newStyle);
}
}
}
 
Back
Top