Paul Tracy
Paul Tracy

Reputation: 33

Can you deserialize and older version of xml into a newer structure

I am attempting to update the objects that being deserialized. Taking one object, renaming, using parts and adding a new object that will hold the remaining objects. Is there a way in .NET to deserialize the old XML into the new format?

e.g.

Old Structure:

<objectinfo>
<element1></element1>
<element2></element2>
<element3></element3>
<element4></element4>
</objectinfo>

New Structure:

<objinfo>
<element1></element1>
<element2></element2>
</objinfo>  

<newobject>
<element3></element3>
<element4></element4>
</newobject>

Note I am using XmlSerializer to deserialize.

Upvotes: 3

Views: 124

Answers (1)

dbc
dbc

Reputation: 117086

Assuming you are using XmlSerializer to deserialize, if your <objectinfo> is an immediate child of the root XML element, then deserializing to some DTO type identical to old type and mapping to the new object manually or via solves the problem quite easily.

If, however, the objects being modified are nested deeply inside an object hierarchy being deserialized then the DTO strategy isn't as convenient because XmlSerializer doesn't offer a general surrogate DTO replacement mechanism. One alternative approach in such situations is to manually handle the unknown elements in an XmlSerializer.UnknownElement event.

To do this in a general way, introduce the following interface and extension methods for XML deserialization:

public interface IUnknownElementHandler
{
    void OnUnknownElement(object sender, XmlElementEventArgs e);
}

public static partial class XmlSerializationHelper
{
    public static T LoadFromXml<T>(this string xmlString, XmlSerializer serializer = null)
    {
        serializer = serializer ?? new XmlSerializer(typeof(T)).AddUnknownElementHandler();
        using (var reader = new StringReader(xmlString))
            return (T)serializer.Deserialize(reader);
    }

    public static T LoadFromFile<T>(string filename, XmlSerializer serializer = null)
    {
        serializer = serializer ?? new XmlSerializer(typeof(T)).AddUnknownElementHandler();
        using (var reader = new FileStream(filename, FileMode.Open))
            return (T)serializer.Deserialize(reader);
    }
    
    public static XmlSerializer AddUnknownElementHandler(this XmlSerializer serializer)
    {
        serializer.UnknownElement += (o, e) =>
        {
            var handler = e.ObjectBeingDeserialized as IUnknownElementHandler;
            if (handler != null)
                handler.OnUnknownElement(o, e);
        };
        return serializer;
    }
}

Then, assuming your new data model looks e.g. like this, where Root is the top-level object and ContainerType contains the elements being restructured:

[XmlRoot(ElementName = "Root")]
public class Root
{
    public ContainerType ContainerType { get; set; }
}

[XmlRoot(ElementName = "ContainerType")]
public partial class ContainerType 
{
    [XmlElement(ElementName = "objinfo")]
    public Objinfo Objinfo { get; set; }
    [XmlElement(ElementName = "newobject")]
    public Newobject Newobject { get; set; }
}

[XmlRoot(ElementName = "objinfo")]
public class Objinfo
{
    [XmlElement(ElementName = "element1")]
    public string Element1 { get; set; }
    [XmlElement(ElementName = "element2")]
    public string Element2 { get; set; }
}

[XmlRoot(ElementName = "newobject")]
public class Newobject
{
    [XmlElement(ElementName = "element3")]
    public string Element3 { get; set; }
    [XmlElement(ElementName = "element4")]
    public string Element4 { get; set; }
}

Add an OnUnknownElement handler to ContainerType as follows:

public partial class ContainerType : IUnknownElementHandler
{
    #region IUnknownElementHandler Members

    void IUnknownElementHandler.OnUnknownElement(object sender, XmlElementEventArgs e)
    {
        var container = (ContainerType)e.ObjectBeingDeserialized;
    
        var element1 = e.Element.SelectSingleNode("element1");
        var element2 = e.Element.SelectSingleNode("element2");
        
        if (element1 != null || element2 != null)
        {
            container.Objinfo = container.Objinfo ?? new Objinfo();
            if (element1 != null)
                container.Objinfo.Element1 = element1.InnerText;
            if (element2 != null)
                container.Objinfo.Element2 = element2.InnerText;
        }
        
        var element3 = e.Element.SelectSingleNode("element3");
        var element4 = e.Element.SelectSingleNode("element4");
        if (element3 != null || element4 != null)
        {
            container.Newobject = container.Newobject ?? new Newobject();
            if (element3 != null)
                container.Newobject.Element3 = element3.InnerText;
            if (element4 != null)
                container.Newobject.Element4 = element4.InnerText;
        }
    }

    #endregion
}

Then when you deserialize your Root from a file using the LoadFromFile method above:

var root = XmlSerializationHelper.LoadFromFile<Root>(filename);

The obsolete, unknown XML elements will postprocessed by the ContainerType handler.

Demo fiddle here.

Upvotes: 2

Related Questions