How to deserialize from XML after adding an attribute?

0

I had a class similar to this:

public class Script
{
    public string Name { get; set; }
    public string HomeKey { get; set; } = "";
}

which would deserialize from an XML file like this:

<Script>
    <Name>ScriptName</Name>
    <HomeKey>KeyText</HomeKey>
</Script>

This works fine using the following code:

    [Test]
    public void DeserializeScriptTest()
    {
        // Arrange
        var contents = Helpers.LoadEmbeddedResourceFileContents("test.xml");
        var serializer = new XmlSerializer(typeof(Script));
        Script s;

        // Act
        using (var reader = new StringReader(contents))
        {
            s = (Script)serializer.Deserialize(reader);
        }

        // Assert
        Assert.AreEqual("KeyText", s.HomeKey);
    }

And I have lots of client code out there that uses these files and will continue to use them into the future.

Now my XML file has changed slightly so that it looks like this:

<Script>
    <Name>ScriptName</Name>
    <HomeKey KeyId="12345">KeyText</HomeKey>
</Script>

My old clients should still be able to deserialize this file as the old HomeKey is still in the same place. New clients need to deserialize the attribute value too. So I created a new class:

public class ScriptV2 : Script
{
    public new HomeKeyV2 HomeKey { get; set; } = new HomeKeyV2();
}

public class HomeKeyV2
{
    [XmlAttribute]
    public int KeyId { get; set; }
    [XmlText]
    public string Value { get; set; } = "";
}

The old code works with the new files just fine. However, the new code doesn't work.

When I run this:

[Test]
public void DeserializeScriptV2Test()
{
    // Arrange
    var contents = Helpers.LoadEmbeddedResourceFileContents("test.xml");

    var serializer = new XmlSerializer(typeof(ScriptV2), new XmlRootAttribute("Script")); // <-- Exception here
    ScriptV2 s;

    // Act
    using (var reader = new StringReader(contents))
    {
        s = (ScriptV2)serializer.Deserialize(reader);
    }

    // Assert
    Assert.AreEqual("KeyText", s.HomeKey.Value);
}

I get an exception on the marked line:

System.InvalidOperationException
  HResult=0x80131509
  Message=There was an error reflecting type 'Project.Classes.ScriptV2'.
  Source=System.Xml
  StackTrace:
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportElement(TypeModel model, XmlRootAttribute root, String defaultNamespace, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, String defaultNamespace, String location, Evidence evidence)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, XmlRootAttribute root)
   at Project.Tests.DeserializeScriptV2Test() in foobarTests.cs:line 12

Inner Exception 1:
InvalidOperationException: There was an error reflecting property 'HomeKey'.

Inner Exception 2:
InvalidOperationException: Member ScriptV2.HomeKey of type Project.Classes.HomeKeyV2 hides base class member Script.HomeKey of type System.String. Use XmlElementAttribute or XmlAttributeAttribute to specify a new name.

How can I change ScriptV2 so that it works with the new structure of the xml file? I can't change Script.cs or deploy updated code to the old clients. They are all working fine with the altered XML files.

c#
xml
serialization
asked on Stack Overflow Oct 1, 2019 by dylanT

1 Answer

1

This works for me

[XmlRoot(ElementName = "HomeKey")]
public class HomeKey
{
   [XmlAttribute(AttributeName = "KeyId")]
   public string KeyId { get; set; }
   [XmlText]
   public string Text { get; set; }
}

[XmlRoot(ElementName = "Script")]
public class ScriptV2
{
   [XmlElement(ElementName = "Name")]
   public string Name { get; set; }
   [XmlElement(ElementName = "HomeKey")]
   public HomeKey HomeKey { get; set; }
}

Usage

var contents = "<Script>\r\n    <Name>ScriptName</Name>\r\n    <HomeKey KeyId=\"12345\">KeyText</HomeKey>\r\n</Script>";

var serializer = new XmlSerializer(typeof(ScriptV2), new XmlRootAttribute("Script"));

using (var reader = new StringReader(contents))
{
   var s = (ScriptV2)serializer.Deserialize(reader);
}
answered on Stack Overflow Oct 1, 2019 by TheGeneral

User contributions licensed under CC BY-SA 3.0