Home > Net >  How to deserialize old XML files after some properties have been renamed?
How to deserialize old XML files after some properties have been renamed?

Time:02-20

For simplicity, suppose I have this class:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

And I use the two following methods for serialization/deserialization:

public void Serialize(Person person, string outputFilePath)
{
    using (var stream = 
           new FileStream(outputFilePath, FileMode.Create, FileAccess.Write))
    {
        var serializer = new XmlSerializer(person.GetType());
        serializer.Serialize(stream, person);
    }
}

public Person Deserialize(string filePath)
{
    using (var stream = new FileStream(filePath, FileMode.Open))
    {
        var serializer = new XmlSerializer(typeof(Person));
        return (Person)serializer.Deserialize(stream);
    }
}

Now, let's say I serialize some Person objects into XML files and then I rename one or more property of the Person class:

// Renamed from `Name`
public string FullName { get; set; }

My goal is to allow the program to still deserialize from those old XML files that has a Name element, instead of FullName

The XmlElementAttribute won't help here:

// If I do this, I can't deserialize from files created after
// the property has been renamed.
[XmlElement("Name")]
public string FullName { get; set; }

It will be ideal if I can support multiple old names, perhaps using some attribute. E.g.,

[XmlAlternateDeserializationElement("Name")]
[XmlAlternateDeserializationElement("Label")]
public string FullName { get; set; }

But any other way would suffice. How can I achieve that?

CodePudding user response:

The following shows how one can deserialize an XML filename when one or more property names have changed.

Note: In the code below, when the XML is serialized, it will be saved in the newer format.

XML - Original

<Person>
    <Id>1</Id>
    <Name>John Smith</Name>
    <City>Some City</City>
</Person>

XmlPersonOriginal:

using System.Xml.Serialization;

namespace XmlSerializationRenamedProperty
{
    [XmlRoot(ElementName = "Person", IsNullable = false)]
    public class XmlPersonOriginal
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string City { get; set; }
        public string StateOrProvince { get; set; }
    }
}

Serialize

public static void SerializeObjectToXMLFile(object obj, string xmlFilename)
{
    using (System.IO.TextWriter xmlStream = new System.IO.StreamWriter(xmlFilename))
    {
        System.Xml.Serialization.XmlSerializerNamespaces ns = new System.Xml.Serialization.XmlSerializerNamespaces();
        ns.Add(string.Empty, "urn:none");

        //create new instance
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());

        //write to XML file
        serializer.Serialize(xmlStream, obj, ns);
    }
}

Usage (Serialize):

string filename = @"C:\Temp\Person.xml";
XmlPersonOriginal pc = new XmlPersonOriginal() { Id = 1, Name = "John Smith", City = "Some City"};
Helper.SerializeObjectToXMLFile2(pc, filename);

Deserialize

public static T DeserializeXMLFileToObject<T>(string xmlFilename)
{
    T rObject = default(T);

    using (System.IO.StreamReader xmlStream = new System.IO.StreamReader(xmlFilename))
    {
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
        rObject = (T)serializer.Deserialize(xmlStream);
    }

    return rObject;
}

Usage (Deserialize):

string filename = @"C:\Temp\Person.xml";
XmlPersonOriginal pc = Helper.DeserializeXMLFileToObject<XmlPersonOriginal>(filename);

Now, if one decides to change the property name from Name to FullName, one can create a new class (XmlPerson_v2) that inherits from the original (XmlPersonOriginal) and override any property name that has been changed. In order to be able to override the property, add the keyword "virtual" to the property.

XmlPersonOriginal:

using System.Xml.Serialization;

namespace XmlSerializationRenamedProperty
{
    [XmlRoot(ElementName = "Person", IsNullable = false)]
    public class XmlPersonOriginal
    {
        public int Id { get; set; }
        public virtual string Name { get; set; }
        public string City { get; set; }
        public string StateOrProvince { get; set; }
    }
}

XmlPerson_v2:

using System;
using System.Xml.Serialization;

namespace XmlSerializationRenamedProperty
{
    [XmlRoot(ElementName = "Person", IsNullable = false)]
    public class XmlPerson_v2 : XmlPersonOriginal
    {
        public string FullName { get; set; }

        #pragma warning disable CS0809
        [XmlIgnore]
        [Obsolete("Name is deprecated, use FullName instead.")]
        public override string Name 
        {
            get
            {
                return base.Name;
            }

            set
            {
                FullName = value;
            }
        }
    }
}

Usage (Serialize):

string filename = @"C:\Temp\PersonModified.xml";
XmlPerson_v2 p2 = new XmlPerson_v2() { Id = 1, FullName = "John Smith", City = "Some City"};
Helper.SerializeObjectToXMLFile2(pc, filename);

Usage (Deserialize):

string filename = @"C:\Temp\Person.xml";
XmlPerson_v2 p2 = Helper.DeserializeXMLFileToObject1b<XmlPerson_v2>(filename);

Usage (Deserialize):

string filename = @"C:\Temp\PersonModified.xml";
XmlPerson_v2 p2 = Helper.DeserializeXMLFileToObject1b<XmlPerson_v2>(filename);

Resources

CodePudding user response:

You can make use of the UnknownElement / UnknownAttribute events of the XmlSerializer to handle the old element names or attribute names:

var serializer = new XmlSerializer(typeof(Person));
serializer.UnknownElement  = (sender, e) =>
{
   var person = (Person)e.ObjectBeingDeserialized;
   if (e.Element.Name == "Name")
   {
       person.FullName = e.Element.InnerText;
   }
};

This keeps your actual data classes clean and concentrates the compatibiliy code in the serialization method.

  • Related