Reputation: 2761
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
Reputation: 2124
There are a couple of problems in your example:
Please find additional details about problems below:
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.
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
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
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