Using Xmlserializer to Serialize Derived Classes

C# XML serialization of derived classes


[XmlInclude(typeof(Square))]
public abstract class Shape {...}

(repeat for all known subtypes)

If the types are only known at runtime, you can supply them to the XmlSerializer constructor, but: then it is important to cache and reuse that serializer instance; otherwise you will haemorrhage dynamically created assemblies. It does this automatically when you use the constructor that just takes a Type, but not for the other overloads.

How to use XmlSerializer to serialize derived instances?

The problem you are seeing can be reproduced with the following minimal example:

public class BaseClass
{
}

public class DerivedClass : BaseClass, IXmlSerializable
{
#region IXmlSerializable Members

public XmlSchema GetSchema() { return null; }

public void ReadXml(XmlReader reader) { throw new NotImplementedException(); }

public void WriteXml(XmlWriter writer) { }

#endregion
}

Using the serialization code:

BaseClass baseClass = new DerivedClass();

using (var textWriter = new StringWriter())
{
using (var xmlWriter = XmlWriter.Create(textWriter))
{
var serializer = new XmlSerializer(typeof(BaseClass), new Type[] { typeof(DerivedClass) });
serializer.Serialize(xmlWriter, baseClass);
}
Console.WriteLine(textWriter.ToString());
}

The following exception is thrown (sample fiddle #1):

System.InvalidOperationException: There was an error generating the XML document. 
---> System.InvalidOperationException: The type DerivedClass may not be used in this context. To use DerivedClass as a parameter, return type, or member of a class or struct, the parameter, return type, or member must be declared as type DerivedClass (it cannot be object). Objects of type DerivedClass may not be used in un-typed collections, such as ArrayLists.

This is among the most unhelpful exception messages I have seen from XmlSerializer. To understand the exception, you need to understand how XmlSerializer handles polymorphism via the [XmlInclude] mechanism. If I remove IXmlSerializable from DerivedClass, the following XML is generated (fiddle #2):

<BaseClass xsi:type="DerivedClass" />

Notice the xsi:type type attribute? That is a w3c standard attribute that XmlSerializer uses to explicitly assert the type of a polymorphic element; it is documented here. When XmlSerializer is deserializing a polymorphic type to which [XmlInclude] attributes have been applied (either statically or through the constructor you are using), it will look for the xsi:type attribute to determine the actual type to construct and deserialize.

It is this, apparently, which conflicts with IXmlSerializable. A type which implements this interface should completely control its XML reading and writing. However, by parsing and interpreting the xsi:type attribute, XmlSerializer has already begun automatic deserialization, and so throws an exception due to the inconsistent deserialization strategies of the base and derived types.

What's more, adding IXmlSerializable to the base type doesn't really fix the problem either If you do so, the xsi:type attribute is never written, and later, when ReadXml() is called, an object of the base type will get unconditionally constructed, as shown in fiddle #3.

(It's conceivable that Microsoft could have implemented a special case where XmlSerializer begins automatic deserialization, then "backs off" and hands the task over to ReadXml() when an IXmlSerializable polymorphic type is encountered and constructed. But, they did not.)

The solution would seem to be to serialize your Filter types automatically using the [XmlInclude] mechanism. In fact I don't see any reason you need to use IXmlSerializable, and was able to serialize your model successfully by removing IXmlSerializable completely and making some minor changes to namespaces:

public static class XmlNamespaces
{
public const string OpengisWfs = "http://www.opengis.net/wfs";
}

[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Query
{
public Filter Filter { get; set; }
}

[XmlInclude(typeof(PropertyIsOpFilter))]
[XmlInclude(typeof(OpFilterBase))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Filter
{
[XmlElement]
public Filter And { get; set; }
}

[XmlInclude(typeof(PropertyIsEqualToFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsOpFilter : Filter
{
public Filter LeftOp { get; set; }

public Filter RightOp { get; set; }
}

[XmlRoot("IsEqualTo", Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsEqualToFilter : PropertyIsOpFilter { }

[XmlInclude(typeof(LiteralFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class OpFilterBase : Filter
{
public string Op { get; set; }
public object Value { get; set; }
}

[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class LiteralFilter : OpFilterBase { }

Notes:

  • For the [XmlInclude] mechanism to work, all the included types apparently must be in the same XML namespace as the base type. To ensure this I added [XmlRoot(Namespace = XmlNamespaces.OpengisWfs)] to all the Filter subtypes.

  • The [XmlInclude(typeof(DerivedType))] attributes can be added either to their immediate parent type or to the lowest common base type. In the code above I added the attributes to the immediate parent types so that members of an intermediate type could be serialized successfully, e.g.:

    public class SomeClass
    {
    PropertyIsOpFilter IsOpFilter { get; set; }
    }
  • Consider marking intermediate types that cannot be instantiated as abstract, e.g. public abstract class Filter. Consider marking types that are "most derived" as sealed, e.g. public sealed class LiteralFilter

  • If you use the new XmlSerializer(Type, Type []) constructor, you must statically cache the serializer to avoid a severe memory leak, as explained here. It's not necessary in my solution but you are using it in your question.

Sample fiddle #4 showing that the following XML is generated successfully:

<Query xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wfs">
<Filter xsi:type="PropertyIsEqualToFilter">
<LeftOp xsi:type="LiteralFilter">
<Value xsi:type="xsd:int">1</Value>
</LeftOp>
<RightOp xsi:type="LiteralFilter">
<Value xsi:type="xsd:int">1</Value>
</RightOp>
</Filter>
</Query>

How to use derived class for XML serialization?

Finally flipping figured most of this out.

I stepped back and started with a very simple working non-derived example and worked up to what I needed.

There were two things going on here. First the clashing type names, then the clashing property names. While I had bits of each of these right, the amount of permutations of options for structuring each when combined together had me confused.

To prevent the abstract and derived type names from clashing when serialized I needed to make the derived class type anonymous (here using the XmlType attribute).

To stop the property names clashing I needed to ignore both the property in the derived class and the base class. To do this without editing the base class I was missing a vital piece, XmlAttributeOverrides. I had seen this mentioned in the MSDN documentation for XmlSerializer.Serialize() but the information there was pretty minimal in explaining what it pertained to. This answer to another question led me to David Woodward's excellent explanation.

I have yet to try any of this with a derived type list property, or with deserialization.

Below is complete basic example of a program that outputs a string with some serialized XML on the console output:

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

namespace Test
{
class Program
{
static void Main(string[] args)
{
var TestBar = new MyXml.Bar()
{
Name = "John Smith",
};

Serializer s = new MyXml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
}
}

public abstract class Bar
{
public abstract string Name { get; set; }
}

public abstract class Serializer
{
public abstract string Serialize(Bar bar);
}

namespace MyXml
{
public class Serializer : Test.Serializer
{
public override string Serialize(Test.Bar bar)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);

// Ignore the Name property in the *base* class!
var ao = new XmlAttributeOverrides();
var a = new XmlAttributes();
a.XmlElements.Clear(); // Clear any element attributes
a.XmlAttribute = null; // Remove any attribute attributes
a.XmlIgnore = true; // Set the ignore attribute value true
ao.Add(typeof(Test.Bar), "Name", a); // Set to use with Test.Bar.Name

using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
var s = new XmlSerializer(typeof(Bar), ao);
s.Serialize(Writer, bar);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}

[Serializable]
[XmlType(AnonymousType = true)] // Make type anonymous!
[XmlRoot(IsNullable = false)]
public class Bar : Test.Bar
{
[XmlIgnore] // Ignore the Name property in the *derived* class!
public override string Name
{
get => Unreverse(ReverseName);
set => ReverseName = Reverse(value);
}

[XmlElement("Name", IsNullable = false)]
public string ReverseName { get; set; }

private string Unreverse(string name)
{
return "John Smith"; // Smith, John -> John Smith
}

private string Reverse(string name)
{
return "Smith, John"; // John Smith -> Smith, John
}
}
}
}

Serialize a list of objects inheriting from class A to xml, so the names of the elements in the xml are B,C,D

Firstly DataContractSerializer does not have a mechanism to support collection item polymorphism by changing collection element name(s). It only supports the known type mechanism which uses the i:type attribute - which you indicate is not acceptable.

Since you are willing to switch to XmlSerializer, you could use the attribute XmlArrayItemAttribute.Type to specify element names for polymorphic types in lists:

public class AListObject
{
[XmlArrayItem(typeof(B))]
[XmlArrayItem(typeof(C))]
public List<A> SerializedListObjects { get; set; }
}

However, you also indicate that the polymorphic subtypes cannot be declared statically at compile type because they exist in some other assembly.

As a result, you will need to use the XmlAttributeOverrides mechanism to specify all possible derived types for all List<A> properties in runtime, and manually construct an XmlSerializer using those overrides.

Here is a prototype solution. First, let's assume you have a root object that refers to an object containing a List<A> like so:

public class RootObject
{
public AListObject AList { get; set; }
}

public class AListObject
{
public List<A> SerializedListObjects { get; set; }
}

(The root object could be the object with the List<A> property, but doesn't need to be.) Let's also assume you know all such objects like AListObject that may contain List<A> properties.

With those assumptions, the following serializer factory can be used to generate an XmlSerializer for any root object that may refer to any instances of the known types containing a List<A> property:

public interface IXmlSerializerFactory
{
XmlSerializer CreateSerializer(Type rootType);
}

public static class AListSerializerFactory
{
static readonly XmlArrayItemTypeOverrideSerializerFactory instance;

static AListSerializerFactory()
{
// Include here a list of all types that have a List<A> property.
// You could use reflection to find all such public types in your assemblies.
var declaringTypeList = new []
{
typeof(AListObject),
};

// Include here a list of all base types with a corresponding mapping
// to find all derived types in runtime. Here you could use reflection
// to find all such types in your assemblies, as shown in
// https://stackoverflow.com/questions/857705/get-all-derived-types-of-a-type
var derivedTypesList = new Dictionary<Type, Func<IEnumerable<Type>>>
{
{ typeof(A), () => new [] { typeof(B), typeof(C) } },
};
instance = new XmlArrayItemTypeOverrideSerializerFactory(declaringTypeList, derivedTypesList);
}

public static IXmlSerializerFactory Instance { get { return instance; } }
}

public class XmlArrayItemTypeOverrideSerializerFactory : IXmlSerializerFactory
{
// To avoid a memory & resource leak, the serializers must be cached as explained in
// https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer

readonly object padlock = new object();
readonly Dictionary<Type, XmlSerializer> serializers = new Dictionary<Type, XmlSerializer>();
readonly XmlAttributeOverrides overrides;

public XmlArrayItemTypeOverrideSerializerFactory(IEnumerable<Type> declaringTypeList, IEnumerable<KeyValuePair<Type, Func<IEnumerable<Type>>>> derivedTypesList)
{
var completed = new HashSet<Type>();
overrides = declaringTypeList
.SelectMany(d => derivedTypesList.Select(p => new { declaringType = d, itemType = p.Key, derivedTypes = p.Value() }))
.Aggregate(new XmlAttributeOverrides(), (a, d) => a.AddXmlArrayItemTypes(d.declaringType, d.itemType, d.derivedTypes, completed));
}

public XmlSerializer CreateSerializer(Type rootType)
{
lock (padlock)
{
XmlSerializer serializer;
if (!serializers.TryGetValue(rootType, out serializer))
serializers[rootType] = serializer = new XmlSerializer(rootType, overrides);
return serializer;
}
}
}

public static partial class XmlAttributeOverridesExtensions
{
public static XmlAttributeOverrides AddXmlArrayItemTypes(this XmlAttributeOverrides overrides, Type declaringType, Type itemType, IEnumerable<Type> derivedTypes)
{
return overrides.AddXmlArrayItemTypes(declaringType, itemType, derivedTypes, new HashSet<Type>());
}

public static XmlAttributeOverrides AddXmlArrayItemTypes(this XmlAttributeOverrides overrides, Type declaringType, Type itemType, IEnumerable<Type> derivedTypes, HashSet<Type> completedTypes)
{
if (overrides == null || declaringType == null || itemType == null || derivedTypes == null || completedTypes == null)
throw new ArgumentNullException();
XmlAttributes attributes = null;
for (; declaringType != null && declaringType != typeof(object); declaringType = declaringType.BaseType)
{
// Avoid duplicate overrides.
if (!completedTypes.Add(declaringType))
break;
foreach (var property in declaringType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
{
// Skip the property if already ignored
if (property.IsDefined(typeof(XmlIgnoreAttribute), false))
continue;

// See if it is a list property, and if so, get its type.
var propertyItemType = property.PropertyType.GetListType();
if (propertyItemType == null)
continue;

// OK, its a List<itemType>. Add all the necessary XmlElementAttribute declarations.
if (propertyItemType == itemType)
{
if (attributes == null)
{
attributes = new XmlAttributes();
foreach (var derivedType in derivedTypes)
// Here we are assuming all the derived types have unique XML type names.
attributes.XmlArrayItems.Add(new XmlArrayItemAttribute { Type = derivedType });
if (itemType.IsConcreteType())
attributes.XmlArrayItems.Add(new XmlArrayItemAttribute { Type = itemType });
}
overrides.Add(declaringType, property.Name, attributes);
}
}
}
return overrides;
}
}

public static class TypeExtensions
{
public static bool IsConcreteType(this Type type)
{
return !type.IsAbstract && !type.IsInterface;
}

public static Type GetListType(this Type type)
{
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}

Then, you can serialize and deserialize instances of RootObject to and from XML as follows:

var root = new RootObject
{
AList = new AListObject
{
SerializedListObjects = new List<A> { new B(), new C() },
},
};

var serializer = AListSerializerFactory.Instance.CreateSerializer(root.GetType());

var xml = root.GetXml(serializer);
var root2 = xml.LoadFromXml<RootObject>(serializer);

Using the extension methods:

public static class XmlSerializationHelper
{
public static T LoadFromXml<T>(this string xmlString, XmlSerializer serial = null)
{
serial = serial ?? new XmlSerializer(typeof(T));
using (var reader = new StringReader(xmlString))
{
return (T)serial.Deserialize(reader);
}
}

public static string GetXml<T>(this T obj, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj);
return textWriter.ToString();
}
}
}

And the result is:

<RootObject xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AList>
<SerializedListObjects>
<B />
<C />
</SerializedListObjects>
</AList>
</RootObject>

Notes:

  • As explained in Memory Leak using StreamReader and XmlSerializer, you must statically cache any XmlSerializer constructed with XmlAttributeOverrides to avoid a severe memory leak. The documentation suggests using a Hashtable, however XmlAttributeOverrides does not override Equals() or GetHashCode(), and does not provide enough access to its internal data for applications developers to write their own. Thus it's necessary to hand-craft some sort of static caching scheme whenever XmlAttributeOverrides is used.

  • Given the complexity of finding and overriding the XmlArrayItem attributes of all List<A> properties, you might consider sticking with the existing i:type mechanism. It's simple, works well, is supported by both DataContractSerializer and XmlSerializer, and is standard.

  • I wrote the class XmlArrayItemTypeOverrideSerializerFactory in a generic way, which adds to the apparent complexity.

Working sample .Net fiddle here.

Using XmlSerializer to serialize derived classes

There are three ways of doing this; either you can use [XmlInclude] against the type, or you can use XmlElement/XmlArrayItem against the property. They are all shown below; uncomment the pair you prefer:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
public class MyWrapper {
//2: [XmlElement("A", Type = typeof(ChildA))]
//2: [XmlElement("B", Type = typeof(ChildB))]
//3: [XmlArrayItem("A", Type = typeof(ChildA))]
//3: [XmlArrayItem("B", Type = typeof(ChildB))]
public List<ChildClass> Data { get; set; }
}
//1: [XmlInclude(typeof(ChildA))]
//1: [XmlInclude(typeof(ChildB))]
public abstract class ChildClass {
public string ChildProp { get; set; }
}
public class ChildA : ChildClass {
public string AProp { get; set; }
}
public class ChildB : ChildClass {
public string BProp { get; set; }
}
static class Program {
static void Main() {
var ser = new XmlSerializer(typeof(MyWrapper));
var obj = new MyWrapper {
Data = new List<ChildClass> {
new ChildA { ChildProp = "abc", AProp = "def"},
new ChildB { ChildProp = "ghi", BProp = "jkl"}}
};
ser.Serialize(Console.Out, obj);
}
}

Serialize/Deserialize derived class as base class

Your problem is that you are constructing your XmlSerializer inconsistently during serialization and deserialization. You need to construct it using the same Type argument in both cases, specifically the base type typeof(Device). Thus I'd suggest you replace your existing completely general serialization method with one specific for a Device:

public static class DeviceExtensions
{
public static string SerializeDevice<TDevice>(this TDevice o) where TDevice : Device
{
// Ensure that [XmlInclude(typeof(TDevice))] is present on Device.
// (Included for clarity -- actually XmlSerializer will make a similar check.)
if (!typeof(Device).GetCustomAttributes<XmlIncludeAttribute>().Any(a => a.Type == o.GetType()))
{
throw new InvalidOperationException("Unknown device type " + o.GetType());
}
var serializer = new XmlSerializer(typeof(Device)); // Serialize as the base class
using (var stringWriter = new StringWriterWithEncoding(Encoding.UTF8))
{
serializer.Serialize(stringWriter, o);
return stringWriter.ToString();
}
}

public static Device DeserializeDevice(this string xml)
{
var serial = new XmlSerializer(typeof(Device));
using (var reader = new StringReader(xml))
{
return (Device)serial.Deserialize(reader);
}
}
}

Then, apply [XmlInclude(typeof(TDevice))] to Device for all possible subtypes:

[XmlInclude(typeof(WindowsDevice))]
[XmlInclude(typeof(AndroidDevice))]
public abstract class Device
{
}

Then both types of devices can now be serialized and deserialized successfully while retaining their type, because XmlSerializer will include an "xsi:type" attribute to explicitly indicate the type:

<Device xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:type="WindowsDevice" />

Or

<Device xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:type="AndroidDevice" />

Sample fiddle.

Updates

So the issue was, that I serialized with typeof(WindowsDevice) instead of typeof(Device)?

Yes.

Any ideas for a solution which will work, if I have to use typeof(WindowsDevice)? Cause I have hundreds of classes and don't want to use hundreds of different XmlSerializer initializations...

This is more of an architectural question than a howto question. One possibility would be to introduce a custom attribute that you can apply to a class to indicate that any subtypes of that class should always be serialized as the attributed base type. All appropriate [XmlInclude(typeof(TDerivedType))] attributes will also be required:

[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class XmlBaseTypeAttribute : System.Attribute
{
}

[XmlInclude(typeof(WindowsDevice))]
[XmlInclude(typeof(AndroidDevice))]
[XmlBaseType]
public abstract class Device
{
}

Then modify your universal XML serialization code to look up the type hierarchy of the object being serialized for an [XmlBaseType] attribute, and (de)serialize as that type:

public static class XmlExtensions
{
static Type GetSerializedType(this Type type)
{
var serializedType = type.BaseTypesAndSelf().Where(t => Attribute.IsDefined(t, typeof(XmlBaseTypeAttribute))).SingleOrDefault();
if (serializedType != null)
{
// Ensure that [XmlInclude(typeof(TDerived))] is present on the base type
// (Included for clarity -- actually XmlSerializer will make a similar check.)
if (!serializedType.GetCustomAttributes<XmlIncludeAttribute>().Any(a => a.Type == type))
{
throw new InvalidOperationException(string.Format("Unknown subtype {0} of type {1}", type, serializedType));
}
}
return serializedType ?? type;
}

public static string Serialize(this object o)
{
var serializer = new XmlSerializer(o.GetType().GetSerializedType());
using (var stringWriter = new StringWriterWithEncoding(Encoding.UTF8))
{
serializer.Serialize(stringWriter, o);
return stringWriter.ToString();
}
}

public static T Deserialize<T>(this string xml)
{
var serial = new XmlSerializer(typeof(T).GetSerializedType());
using (var reader = new StringReader(xml))
{
return (T)serial.Deserialize(reader);
}
}
}

Of course this means that if your code tries to deserialize XML it expects to contain a WindowsDevice, it might actually get back an AndroidDevice depending upon the contents of the XML.

Sample fiddle #2.

how to serialize a base class variable with a different name in a derived class

From your comment, it appears you are able to make changes to TheBaseClass. Thus you can add a virtual bool ShouldSerialize{PropertyName}() method for the BaseClassList property in the base class and return true. Then override it in the derived class and return false, and introduce a proxy property with the desired name:

public class TheBaseClass
{
public List<int> BaseClassList { get; set; }

public virtual bool ShouldSerializeBaseClassList() { return true; }
}

public class TheDerivedClass : TheBaseClass
{
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public List<int> DerivedClassList { get { return BaseClassList; } set { BaseClassList = value; } }

public override bool ShouldSerializeBaseClassList() { return false; }
}

For an explanation of why this works see Defining Default Values with the ShouldSerialize and Reset Methods.

How to serialize the base class with derived classes

You can't magically serialize a derived class as it's base because

"...Serialization checks type of instance by calling Object.getType()
method. This method always returns the exact type of object."

http://bytes.com/topic/net/answers/809946-how-force-serialize-base-type

The solution here, if you really need to only serialize the base class is to implement the IXmlSerializable interface and create your own custom serializer.

IXmlSerializable:
http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable(v=vs.110).aspx

One more thought. If you can work around the limitation of outputting the extra XML elements, you are able to serialize the derived class using only the base object by either 1) using XmlIncludeAttributes on the base class to tell it which types to expect or 2) using the XmlSerializer constructor overload that takes a list of types.

Edit:
After thinking about this a little more, a workaround would be that you would add a Clone() method onto your base object, then serialize the clone of the base.



Related Topics



Leave a reply



Submit