Search code examples
c#xmlserializationdeserialization

Deserialize XML with optional different name


I have this class definition

public class Foo
{

}

Which deserializes into this xml

<?xml version="1.0"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

</Foo>

This xml file layout already exists on client machines.

I want to rename the class from Foo to Footastic.

public class Footastic
{

}

Any new serialize of the now called class Footastic, should serialize as

<?xml version="1.0"?>
<Footastic xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

</Footastic>

My question is how can I tell the deserialization to accept either Foo or Footastic?

I know I could "rename" the Footastic to Foo by specifing the XmlRoot attribute.

[XmlRoot("Foo")]
public class Footastic
{

}

BUT this would also change any new serialization of the object, this is undesired.

A new serialization should serialize as Footastic ONLY old xml files containing the old name Foo should be supported while deserializing Footastic.

Just in case you need the code for deserializing/serializing. I am not really doing anything fancy.

Deserialize:

public static T? Deserialize<T>(string fileName)
{
    if (!File.Exists(fileName))
        return default;

    using FileStream streamReader = new(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    using var xr = XmlReader.Create(streamReader);
    XmlSerializer xmlDeSerializer = new(typeof(T));
    return (T?)xmlDeSerializer.Deserialize(xr);
}

Serialize:

public static void Serialize<T>(T? value, string fileName)
{
    if (value is null)
        return;

    Directory.CreateDirectory(Path.GetDirectoryName(fileName) ?? "");

    using FileStream fileStream = new(fileName, FileMode.Create, FileAccess.Write, FileShare.Write);
    using var streamWriter = XmlWriter.Create(fileStream, new()
    {
        Encoding = Encoding.UTF8,
        Indent = true
    });

    XmlSerializer xmlSerializer = new(typeof(T));
    xmlSerializer.Serialize(streamWriter, value);
}

Solution

  • Try the following approach.
    Create a custom XML reader that replaces the name Foo with Footastic on the fly.

    public class FooReader : XmlTextReader
    {
        // Add other constructor overloads as needed.
        public FooReader(string url) : base(url) { }
    
        public override string LocalName
        {
            get
            {
                if (base.LocalName == "Foo")
                    return "Footastic";
                return base.LocalName;
            }
        }
    }
    

    Use it like this.

    var ser = new XmlSerializer(typeof(Footastic));
    
    using var fooReader = new FooReader("test.xml");
    
    var foo = (Footastic)ser.Deserialize(fooReader);
    

    This will accept both Foo and Footastic.

    However, be careful! This will replace all Foo elements!
    If you have elements with the same name in xml in other places, then you need to make additional checks.