0

I am facing a challenge deserializing an XML to appropriate type. Request your help.

I have two xml files. oldXML.xml and newXML.xml below respectively

  <?xml version="1.0"?>
  <root>
    <elementOne>101</elementOne>
    <elementTwo>10</elementTwo>
  </root>

And

<?xml version="1.0"?>
  <root>
    <elementOne>101</elementOne>
    <elementTwo>10</elementTwo>
    <elementThree>10</elementThree>
  </root>

newXML.xml has an additional attribute "elementThree"

I have written 3 classes to desirialize the XMLs into

    public abstract class ResponseBase
    {
        public abstract void PrintResult();

    }
    public class OldXML : ResponseBase
    {
        [XmlElement("elementOne")]
        public string ElementOne { get; set; }
        [XmlElement("elementTwo")]
        public string ElementTwo { get; set; }

        public override void PrintResult()
        {
            Console.WriteLine("Result is of type 'OldXML': {0}, {1}", ElementOne, ElementTwo);
        }
    }

    public class NewXML : ResponseBase
    {
        [XmlElement("elementOne")]
        public string ElementOne { get; set; }
        [XmlElement("elementTwo")]
        public string ElementTwo { get; set; }
        [XmlElement("elementThree")]
        public string ElementThree { get; set; }

        public override void PrintResult()
        {
            Console.WriteLine("Result is of type 'NewXML': {0}, {1}, {2}", ElementOne, ElementTwo, ElementThree);
        }
    }

And I want to deserialize them as below

    ResponseBase result1= MethodToDeserializeToBeWritten(File.ReadAllText("oldXML.json"))
    ResponseBase result2= MethodToDeserializeToBeWritten(File.ReadAllText("newXML.json"))
    result1.PrintResult()
    result2.PrintResult()

When I Invoke PrintResult method, at the mercy of polymorphism in OOPS, child class implementation should be invoked (Not working, throws an error that abstract class cannot be instantiated). Please note that these XMLs are just examples and the code should work for any such XMLs.

Also, the XML is received from a client and hence we cannot change the XML.

The reason for doing this is, in future, we might get a new XML with new attribute say "elementFour". For that, we will be adding a new class and not touching the existing implementation.

Thanks in advance.

Varun Shridhar
  • 113
  • 1
  • 9
  • Is the XML format fixed? If not, `XmlSerializer` supports the [`xsi:type`](https://msdn.microsoft.com/en-us/library/ca1ks327.aspx) mechanism for situations like this. See: [Using XmlSerializer to serialize derived classes](https://stackoverflow.com/a/1643424) and [Deserialize XML with multiple types](https://stackoverflow.com/q/50484173). – dbc Oct 03 '19 at 07:42
  • XML is fixed. If it changes, we would be adding a new class (repository pattern) – Varun Shridhar Oct 03 '19 at 08:58
  • And what is your problem? There is not real question in your post. – Ondrej Tucny Oct 03 '19 at 09:19
  • The deserialize while not fail if the XML file is missing properties. So you can always add new properties and older XML will just have a null when a property is missing. – jdweng Oct 03 '19 at 09:19
  • The catch is I don't want to touch the existing classes. new XML new class. – Varun Shridhar Oct 03 '19 at 10:43
  • My problem is I want to write the method "MethodToDeserializeToBeWritten" which will deserialize xml input to appropriate type (i.e. if elementOne and elementTwo attributes are present in the XML the OldXML type and if elementOne and elementTwo and elementThree attributes are present in the XML then NewXML type) – Varun Shridhar Oct 03 '19 at 10:46

1 Answers1

0

You need to create a generic MethodToDeserializeToBeWritten method.

public class XML
{
    public static T Deserialize<T>(string input)
    {
        var serializer = new XmlSerializer(typeof(T));
        using (TextReader textReader = new StringReader(input))
        {
            return (T)serializer.Deserialize(textReader);
        }
    }
}


public abstract class ResponseBase
{
    public abstract void PrintResult();

}

[XmlRoot("root")]
public class OldXML : ResponseBase
{
    [XmlElement("elementOne")]
    public string ElementOne { get; set; }
    [XmlElement("elementTwo")]
    public string ElementTwo { get; set; }

    public override void PrintResult()
    {
        Console.WriteLine("Result is of type 'OldXML': {0}, {1}", ElementOne, ElementTwo);
    }
}

[XmlRoot("root")]
public class NewXML : ResponseBase
{
    [XmlElement("elementOne")]
    public string ElementOne { get; set; }
    [XmlElement("elementTwo")]
    public string ElementTwo { get; set; }
    [XmlElement("elementThree")]
    public string ElementThree { get; set; }

    public override void PrintResult()
    {
        Console.WriteLine("Result is of type 'NewXML': {0}, {1}, {2}", ElementOne, ElementTwo, ElementThree);
    }
}


class Program
{
    static void Main(string[] args)
    {
        string rootFilePath = @"C:\test\";
        ResponseBase result1 = MethodToDeserializeToBeWritten<OldXML>(File.ReadAllText($"{rootFilePath}oldXML.xml"));
        ResponseBase result2 = MethodToDeserializeToBeWritten<NewXML>(File.ReadAllText($"{rootFilePath}newXML.xml"));
        result1.PrintResult();
        result2.PrintResult();
    }

    static ResponseBase MethodToDeserializeToBeWritten<T>(string fileContent) where T : ResponseBase
    {
        return XML.Deserialize<T>(fileContent);
    }
}

OUTPUT:

Result is of type 'OldXML': 101, 10

Result is of type 'NewXML': 101, 10, 10

Or you can get rid off MethodToDeserializeToBeWritten method and simplify your code as follows:

ResponseBase result1 = XML.Deserialize<OldXML>(File.ReadAllText($"{rootFilePath}oldXML.xml"));
ResponseBase result2 = XML.Deserialize<NewXML>(File.ReadAllText($"{rootFilePath}newXML.xml"));

Also note that you need to mark OldXML and NewXML with a XmlRoot attribute. Otherwise, you get an exception.

Community
  • 1
  • 1
Dmitry Stepanov
  • 2,776
  • 8
  • 29
  • 45
  • Thank you. You made my day :-) – Varun Shridhar Oct 03 '19 at 15:41
  • One thing. To do a ResponseBase result1 = XML.Deserialize(File.ReadAllText($"{rootFilePath}oldXML.xml")), I don't know whether I receive oldXml or newXML. I receive it from a method. Based on which xml I receive, can I deserialize to appropriate type? – Varun Shridhar Oct 03 '19 at 15:57
  • You should exactly know what type you are deserializing into. If you can't know it, then deserialize the response into a type that has all possible properties, in your case it's a `NewXML`, so not existing properties will just have null values. – Dmitry Stepanov Oct 03 '19 at 16:51
  • That was what I was afraid of. – Varun Shridhar Oct 04 '19 at 02:12