Xml Serialization and Inherited Types

How to implement Xml Serialization with inherited classes in C#

Unfortunately, you need to tell the XmlSerializer the classes you intend to serialize or deserialize using the XmlArrayItem() attribute. Each different type also needs its own element name. For example:

public class ComponentDerviedClass1: Component
public class ComponentDerivedClass2: Component
public class ComponentDerivedClass3: Component

// ...

public class ComponentsCollection
{
[XmlArray("Components")]
[XmlArrayItem("ComponentDerivedClass1", typeof(ComponentDerivedClass1))]
[XmlArrayItem("ComponentDerivedClass2", typeof(ComponentDerivedClass2))]
[XmlArrayItem("ComponentDerivedClass3", typeof(ComponentDerivedClass3))]
public List<Component> Components
{
// ...
}
}

This would read an XML file like:

<?xml version="1.0" encoding="utf-8" ?>
<ComponentsCollection>
<Components>
<ComponentDerivedClass1>
<!-- ... -->
</ComponentDerivedClass1>
<ComponentDerivedClass2>
<!-- ... -->
</ComponentDerivedClass2>
<ComponentDerivedClass3>
<!-- ... -->
</ComponentDerivedClass3>
</Components>
</ComponentsCollection>

Multiple instances of each element can be present (or none).

XML Serialization and Inherited Types

Problem Solved!

OK, so I finally got there (admittedly with a lot of help from here!).

So summarise:

Goals:

  • I didn't want to go down the XmlInclude route due to the maintenence headache.
  • Once a solution was found, I wanted it to be quick to implement in other applications.
  • Collections of Abstract types may be used, as well as individual abstract properties.
  • I didn't really want to bother with having to do "special" things in the concrete classes.

Identified Issues/Points to Note:

  • XmlSerializer does some pretty cool reflection, but it is very limited when it comes to abstract types (i.e. it will only work with instances of the abstract type itself, not subclasses).
  • The Xml attribute decorators define how the XmlSerializer treats the properties its finds. The physical type can also be specified, but this creates a tight coupling between the class and the serializer (not good).
  • We can implement our own XmlSerializer by creating a class that implements IXmlSerializable .

The Solution

I created a generic class, in which you specify the generic type as the abstract type you will be working with. This gives the class the ability to "translate" between the abstract type and the concrete type since we can hard-code the casting (i.e. we can get more info than the XmlSerializer can).

I then implemented the IXmlSerializable interface, this is pretty straight forward, but when serializing we need to ensure we write the type of the concrete class to the XML, so we can cast it back when de-serializing. It is also important to note it must be fully qualified as the assemblies that the two classes are in are likely to differ. There is of course a little type checking and stuff that needs to happen here.

Since the XmlSerializer cannot cast, we need to provide the code to do that, so the implicit operator is then overloaded (I never even knew you could do this!).

The code for the AbstractXmlSerializer is this:

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace Utility.Xml
{
public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
{
// Override the Implicit Conversions Since the XmlSerializer
// Casts to/from the required types implicitly.
public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
{
return o.Data;
}

public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
{
return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
}

private AbstractType _data;
/// <summary>
/// [Concrete] Data to be stored/is stored as XML.
/// </summary>
public AbstractType Data
{
get { return _data; }
set { _data = value; }
}

/// <summary>
/// **DO NOT USE** This is only added to enable XML Serialization.
/// </summary>
/// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
public AbstractXmlSerializer()
{
// Default Ctor (Required for Xml Serialization - DO NOT USE)
}

/// <summary>
/// Initialises the Serializer to work with the given data.
/// </summary>
/// <param name="data">Concrete Object of the AbstractType Specified.</param>
public AbstractXmlSerializer(AbstractType data)
{
_data = data;
}

#region IXmlSerializable Members

public System.Xml.Schema.XmlSchema GetSchema()
{
return null; // this is fine as schema is unknown.
}

public void ReadXml(System.Xml.XmlReader reader)
{
// Cast the Data back from the Abstract Type.
string typeAttrib = reader.GetAttribute("type");

// Ensure the Type was Specified
if (typeAttrib == null)
throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because no 'type' attribute was specified in the XML.");

Type type = Type.GetType(typeAttrib);

// Check the Type is Found.
if (type == null)
throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because the type specified in the XML was not found.");

// Check the Type is a Subclass of the AbstractType.
if (!type.IsSubclassOf(typeof(AbstractType)))
throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because the Type specified in the XML differs ('" + type.Name + "').");

// Read the Data, Deserializing based on the (now known) concrete type.
reader.ReadStartElement();
this.Data = (AbstractType)new
XmlSerializer(type).Deserialize(reader);
reader.ReadEndElement();
}

public void WriteXml(System.Xml.XmlWriter writer)
{
// Write the Type Name to the XML Element as an Attrib and Serialize
Type type = _data.GetType();

// BugFix: Assembly must be FQN since Types can/are external to current.
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
new XmlSerializer(type).Serialize(writer, _data);
}

#endregion
}
}

So, from there, how do we tell the XmlSerializer to work with our serializer rather than the default? We must pass our type within the Xml attributes type property, for example:

[XmlRoot("ClassWithAbstractCollection")]
public class ClassWithAbstractCollection
{
private List<AbstractType> _list;
[XmlArray("ListItems")]
[XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))]
public List<AbstractType> List
{
get { return _list; }
set { _list = value; }
}

private AbstractType _prop;
[XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))]
public AbstractType MyProperty
{
get { return _prop; }
set { _prop = value; }
}

public ClassWithAbstractCollection()
{
_list = new List<AbstractType>();
}
}

Here you can see, we have a collection and a single property being exposed, and all we need to do is add the type named parameter to the Xml declaration, easy! :D

NOTE: If you use this code, I would really appreciate a shout-out. It will also help drive more people to the community :)

Now, but unsure as to what to do with answers here since they all had their pro's and con's. I'll upmod those that I feel were useful (no offence to those that weren't) and close this off once I have the rep :)

Interesting problem and good fun to solve! :)

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.

deserialize xml into inherited classes from base class

@netwer It is not working for you because the code suggested above generates the xml (below) that does not match with the one you use for deserialization (see how it specifies derived types in for element).

<?xml version="1.0" encoding="utf-8"?>
<Root1>
<name>Name1</name>
<company>Comp1</company>
<url>site.com</url>
<elements>
<element d3p1:type="Element1" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance">
<url>site1.com</url>
<price>15000</price>
<manufacturer_warranty>true</manufacturer_warranty>
<country_of_origin>Япония</country_of_origin>
</element>
<element d3p1:type="Element2" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance">
<url>site2.com</url>
<price>100</price>
<language>lg</language>
<binding>123</binding>
</element>
</elements>
</Root1>

So you will either have to match this format with source xml (change code or API that returns this xml data) or take another approach. Even if you manage to do with former one you have to find a way to access Element1 or Element2 specific properties.

newRoot1.Elements.ElementList[i] will always let you access only price and url since your list is of Element type. Although run time type of ElementList[i] will be Element1 or Element2, how will you detect that?

Here I suggest alternative approach. Irrespective of whether your application (client) generates this xml or the server which returns it on hitting API, you should be able to gather information on all the fields applicable to an 'element' object. If it is your code you know it already, if it is an API there must be a document for it. This way you only have to create one 'Element' (no derived classes) and put proper checks (mostly string.IsNullOrEmpty()) before accessing Element class property values in your code. Only the properties that are present in your xml 'element' element will be considered rest will be set to NULL for that instance.

[Serializable]
public class Element
{
[XmlElement("url")]
public string Url { get; set; }

[XmlElement("price")]
public string Price { get; set; }

[XmlElement("manufacturer_warranty")]
public string mw { get; set; }

[XmlElement("country_of_origin")]
public string co { get; set; }

[XmlElement("language")]
public string lg { get; set; }

[XmlElement("binding")]
public string bind { get; set; }
}

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 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
}
}
}
}


Related Topics



Leave a reply



Submit