XML serialization of interface property

Solution 1:

This is simply an inherent limitation of declarative serialization where type information is not embedded within the output.

On trying to convert <Flibble Foo="10" /> back into

public class Flibble { public object Foo { get; set; } }

How does the serializer know whether it should be an int, a string, a double (or something else)...

To make this work you have several options but if you truly don't know till runtime the easiest way to do this is likely to be using the XmlAttributeOverrides.

Sadly this will only work with base classes, not interfaces. The best you can do there is to ignore the property which isn't sufficient for your needs.

If you really must stay with interfaces you have three real options:

Hide it and deal with it in another property

Ugly, unpleasant boiler plate and much repetition but most consumers of the class will not have to deal with the problem:

[XmlIgnore()]
public object Foo { get; set; }

[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized 
{ 
  get { /* code here to convert any type in Foo to string */ } 
  set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } 
}

This is likely to become a maintenance nightmare...

Implement IXmlSerializable

Similar to the first option in that you take full control of things but

  • Pros
    • You don't have nasty 'fake' properties hanging around.
    • you can interact directly with the xml structure adding flexibility/versioning
  • Cons
    • you may end up having to re-implement the wheel for all the other properties on the class

Issues of duplication of effort are similar to the first.

Modify your property to use a wrapping type

public sealed class XmlAnything<T> : IXmlSerializable
{
    public XmlAnything() {}
    public XmlAnything(T t) { this.Value = t;}
    public T Value {get; set;}

    public void WriteXml (XmlWriter writer)
    {
        if (Value == null)
        {
            writer.WriteAttributeString("type", "null");
            return;
        }
        Type type = this.Value.GetType();
        XmlSerializer serializer = new XmlSerializer(type);
        writer.WriteAttributeString("type", type.AssemblyQualifiedName);
        serializer.Serialize(writer, this.Value);   
    }

    public void ReadXml(XmlReader reader)
    {
        if(!reader.HasAttributes)
            throw new FormatException("expected a type attribute!");
        string type = reader.GetAttribute("type");
        reader.Read(); // consume the value
        if (type == "null")
            return;// leave T at default value
        XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
        this.Value = (T)serializer.Deserialize(reader);
        reader.ReadEndElement();
    }

    public XmlSchema GetSchema() { return(null); }
}

Using this would involve something like (in project P):

public namespace P
{
    public interface IFoo {}
    public class RealFoo : IFoo { public int X; }
    public class OtherFoo : IFoo { public double X; }

    public class Flibble
    {
        public XmlAnything<IFoo> Foo;
    }


    public static void Main(string[] args)
    {
        var x = new Flibble();
        x.Foo = new XmlAnything<IFoo>(new RealFoo());
        var s = new XmlSerializer(typeof(Flibble));
        var sw = new StringWriter();
        s.Serialize(sw, x);
        Console.WriteLine(sw);
    }
}

which gives you:

<?xml version="1.0" encoding="utf-16"?>
<MainClass 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  <RealFoo>
   <X>0</X>
  </RealFoo>
 </Foo>
</MainClass>

This is obviously more cumbersome for users of the class though avoids much boiler plate.

A happy medium may be merging the XmlAnything idea into the 'backing' property of the first technique. In this way most of the grunt work is done for you but consumers of the class suffer no impact beyond confusion with introspection.

Solution 2:

The solution to this is using reflection with the DataContractSerializer. You don't even have to mark your class with [DataContract] or [DataMember]. It will serialize any object, regardless of whether it has interface type properties (including dictionaries) into xml. Here is a simple extension method that will serialize any object into XML even if it has interfaces (note you could tweak this to run recursively as well).

    public static XElement ToXML(this object o)
    {
        Type t = o.GetType();

        Type[] extraTypes = t.GetProperties()
            .Where(p => p.PropertyType.IsInterface)
            .Select(p => p.GetValue(o, null).GetType())
            .ToArray();

        DataContractSerializer serializer = new DataContractSerializer(t, extraTypes);
        StringWriter sw = new StringWriter();
        XmlTextWriter xw = new XmlTextWriter(sw);
        serializer.WriteObject(xw, o);
        return XElement.Parse(sw.ToString());
    }

what the LINQ expression does is it enumerates each property, returns each property that is an interface, gets the value of that property (the underlying object), gets the type of that concrete object puts it into an array, and adds that to the serializer's list of known types.

Now the serializer knows how about the types it is serializing so it can do its job.