Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XML serialization of interface property

I would like to XML serialize an object that has (among other) a property of type IModelObject (which is an interface).

public class Example {     public IModelObject Model { get; set; } } 

When I try to serialize an object of this class, I receive the following error:
"Cannot serialize member Example.Model of type Example because it is an interface."

I understand that the problem is that an interface cannot be serialized. However, the concrete Model object type is unknown until runtime.

Replacing the IModelObject interface with an abstract or concrete type and use inheritance with XMLInclude is possible, but seems like an ugly workaround.

Any suggestions?

like image 338
Elad Avatar asked Aug 26 '09 10:08

Elad


People also ask

What is XML serialization?

XML serialization is the process of converting XML data from its representation in the XQuery and XPath data model, which is the hierarchical format it has in a Db2® database, to the serialized string format that it has in an application.

Is XML a serialization format?

XML serialization serializes only the public fields and property values of an object into an XML stream. XML serialization does not include type information. For example, if you have a Book object that exists in the Library namespace, there is no guarantee that it is deserialized into an object of the same type.

What is the basic difference between SOAP serialization and XML serialization?

XML serialization can also be used to serialize objects into XML streams that conform to the SOAP specification. SOAP is a protocol based on XML, designed specifically to transport procedure calls using XML. To serialize or deserialize objects, use the XmlSerializer class.

What are the types of serialization?

There are three types of serialization in . Net : Binary Serialization, SOAP Serialization and XML Serialization.


2 Answers

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.

like image 171
ShuggyCoUk Avatar answered Sep 25 '22 01:09

ShuggyCoUk


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.

like image 23
Despertar Avatar answered Sep 24 '22 01:09

Despertar