joelc
joelc

Reputation: 2761

XML not deserializing to correct class, and not serializing with xsi:type attribute after

In the following snippet, I'm trying to deserialize XML that has type attributes on Animal records identifying them as either Cat or Dog, both of which inherit from Animal.

When deserialized, these records all appear as Animal.

Then, when attempting to serialize the object (after deserialization), the xsi:type="Dog" and xsi:type="Cat" does not appear in the XML.

I'm not sure if this is due to how I've decorated the classes or in my serializer/deserializer implementation. Preferring if possible a solution in the class rather than the serializer/deserializer wrapper methods.

Code:

using System;  
using System.IO; 
using System.Text;
using System.Xml;
using System.Xml.Serialization; 

namespace sandbox
{
    public partial class Program
    {
        static void Main(string[] args)
        { 
            string xml =
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<OuterClass xmlns=\"http://myschema.com/\">" +
                "   <Animals>" +
                "      <Animal xmlns:xsi=\"http://myschema.com/\" xsi:type=\"Dog\">" +
                "         <Name>Watson</Name>" +
                "         <Height>10</Height>" +
                "         <Weight>10</Weight>" +
                "         <Paws>4</Paws>" +
                "         <Woof>True</Woof>" +
                "      </Animal>" +
                "      <Animal xmlns:xsi=\"http://myschema.com/\" xsi:type=\"Cat\">" +
                "         <Name>Hermy</Name>" +
                "         <Height>10</Height>" +
                "         <Weight>10</Weight>" +
                "         <Paws>4</Paws>" +
                "         <Meow>True</Meow>" +
                "      </Animal>" +
                "   </Animals>" +
                "</OuterClass>";

            OuterClass data = null;

            try
            {
                data = DeserializeXml<OuterClass>(xml);
                foreach (Animal curr in data.Animals) Console.WriteLine(curr.Name + ": " + curr.GetType().ToString());
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                Console.WriteLine(e.Message);
            }

            Console.WriteLine(SerializeXml(data));
            Console.ReadLine(); 
        }

        public static T DeserializeXml<T>(string xml)
        {
            XmlSerializer xmls = new XmlSerializer(typeof(T));
            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xml))) 
                return (T)xmls.Deserialize(ms); 
        }

        public static string SerializeXml(object obj)
        { 
            XmlSerializer xml = new XmlSerializer(obj.GetType()); 
            using (MemoryStream stream = new MemoryStream())
            {
                using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
                {
                    xml.Serialize(writer, obj);
                    byte[] bytes = stream.ToArray();
                    return Encoding.UTF8.GetString(bytes, 0, bytes.Length); 
                }
            }
        }
    }

    [XmlRoot(ElementName = "OuterClass", Namespace = "http://myschema.com/", IsNullable = true)]
    public class OuterClass
    { 
        [XmlArrayItem(Type = typeof(Cat)), XmlArrayItem(Type = typeof(Dog)), XmlArrayItem(Type = typeof(Animal))] 
        public Animal[] Animals { get; set; }
        [XmlAttribute("type")]
        public string Type { get; set; }
    }

    [XmlType(TypeName = "Cat")]
    [XmlRoot(ElementName = "Animal", Namespace = "http://myschema.com/", IsNullable = true)]
    public class Cat : Animal
    { 
        public bool Meow { get; set; }
    }

    [XmlType(TypeName = "Dog")]
    [XmlRoot(ElementName = "Animal", Namespace = "http://myschema.com/", IsNullable = true)]
    public class Dog : Animal
    { 
        public bool Woof { get; set; }
    }

    [XmlInclude(typeof(Cat))]
    [XmlInclude(typeof(Dog))]
    [XmlRoot(ElementName = "Animal", Namespace = "http://myschema.com/", IsNullable = true)]
    public class Animal
    {
        public string Name { get; set; }
        public int Height { get; set; }
        public int Weight { get; set; }
        public int Paws { get; set; }
    }
}

Output:

Watson: sandbox.Animal       <-- should be sandbox.Dog
Hermy: sandbox.Animal        <-- should be sandbox.Cat
?<?xml version="1.0" encoding="utf-8"?>
<OuterClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://myschema.com/">
  <Animals>
    <Animal>                 <-- missing xsi:type, object missing 'Woof' 
      <Name>Watson</Name>
      <Height>10</Height>
      <Weight>10</Weight>
      <Paws>4</Paws>
    </Animal>
    <Animal>                 <-- missing xsi:type, object missing 'Meow'
      <Name>Hermy</Name>
      <Height>10</Height>
      <Weight>10</Weight>
      <Paws>4</Paws>
    </Animal>
  </Animals>
</OuterClass>

Upvotes: 0

Views: 177

Answers (2)

fenixil
fenixil

Reputation: 2124

There are a couple of problems in your example:

  • the model does not match xmls
  • every Animal element overrides xsi namespace
  • boolean values are not supported for serialization

Please find additional details about problems below:

1. model does not match xmls

When you specify XmlArrayItem XmlSerializer will use type name as element name, or you can change it by providing ElementName explicitly. If you annotate array property with XmlArrayItem you'll get the following output:

Console.WriteLine(SerializeXml(new OuterClass { Animals = new Animal[] { new Cat(), new Dog() } }));

<OuterClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://myschema.com/">
  <Animals>
    <Cat>
      <Height>0</Height>
      <Weight>0</Weight>
      <Paws>0</Paws>
      <Meow>false</Meow>
    </Cat>
    <Dog>
      <Height>0</Height>
      <Weight>0</Weight>
      <Paws>0</Paws>
      <Woof>false</Woof>
    </Dog>
  </Animals>
</OuterClass>

If you don't annotate, you'll get output with type defined by xsi:type attribute:

<OuterClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://myschema.com/">
  <Animals>
    <Animal xsi:type="Cat">
      <Height>0</Height>
      <Weight>0</Weight>
      <Paws>0</Paws>
      <Meow>false</Meow>
    </Animal>
    <Animal xsi:type="Dog">
      <Height>0</Height>
      <Weight>0</Weight>
      <Paws>0</Paws>
      <Woof>false</Woof>
    </Animal>
  </Animals>
</OuterClass>

In this case you must add XmlInclude attribute to the base class.

2. every Animal element overrides xsi namespace

http://www.w3.org/2001/XMLSchema-instance is a special namespace defined in W3C that helps serializer to know what type is in the XML element. In your input xml, each Animal element overrides this namespace with a custom http://myschema.com/ so when serializer meets xsi:type="Cat" it has no idea what it means. Wikipedia is a good starting point to read about XML namespaces: https://en.wikipedia.org/wiki/XML_namespace

3. Boolean values in XML

W3C defines boolean data type as ‘true’, ‘false’, ‘0’, and ‘1’ so you'll get an exception if you'll deserialize boolean with value 'True'. You may find workaround options online but I suppose that your input XML is malformed and you need to lowercase boolean values in XML.

Upvotes: 1

jdweng
jdweng

Reputation: 34421

You need to create the classes correctly :

            OuterClass outerClass = new OuterClass()
            {
                Animals = new Animal[] {
                    new Dog() { Name = "Watson", Height = 10, Weight = 10, Paws = 4},
                    new Cat() { Name = "Hermy", Height = 10, Weight = 10, Paws = 4}
                }
            };

Upvotes: 1

Related Questions