locked
XmlSerialization with Interfaces

    Question

  • I'm trying to serialize a class that exposes a property as an interface.  When I try to serialize the instance of the class the XmlSerializer throws an InvalidOperationException with the following message:

    {"Cannot serialize member SerializationTest.ParkingLot.MyCar of type SerializationTest.ICar because it is an interface."}

    This may seem like a silly question but is this true?  I've never ran into this before and I use interfaces pretty extensively in my designs.  Can the .NET XmlSerializer not handle interfaces?

    Thanks!

    Jeremy

    Thursday, August 17, 2006 8:47 PM

Answers

  • You can not serialize an interface.  The problem is that an interface is an opaque type.  There is no way for the serializer to know what to write out and more importantly what to create when it needs to serialize things back. 

    There are a variety of workarounds but ultimately it'll require that you handle serialization yourself.  Assuming that you are using XmlSerializer then your parent class should implement IXmlSerializable.  You can then implement the methods to read and write the fields.  For the interface you can get the actual underlying type and then create a new serializer that serializes the actual type to the XML stream.  You'd do the inverse to deserialize.

    public interface IMyInterface
    {
      
    string Name { get; set; }
    }

    public class MyInterface : IMyInterface
    {
      
    private string m_strName;
      
    public string Name
       {
          
    get { return m_strName ?? ""; }
          
    set { m_strName = value; }
       }
    }

    public class Parent : IXmlSerializable
    {
       
    public Parent ( ) { }

       
    private IMyInterface m_Child;
       
    public IMyInterface Child
        {
          
    get { return m_Child; }
          
    set { m_Child = value; }
        }

        System.Xml.Schema.
    XmlSchema IXmlSerializable.GetSchema ( )
        {
           
    return null;
        }

       
    void IXmlSerializable.ReadXml ( XmlReader reader )
        {
            reader.ReadStartElement(
    "Child");
               
    string strType = reader.GetAttribute("type");
               
    XmlSerializer serial = new XmlSerializer(Type.GetType(strType));
                m_Child = (
    IMyInterface)serial.Deserialize(reader);
            reader.ReadEndElement();
        }

        void IXmlSerializable.WriteXml ( XmlWriter writer )
       
    {
           
    writer.WriteStartElement("Child");
               
    string strType = m_Child.GetType().FullName;
               
    writer.WriteAttributeString("type", strType);
               
    XmlSerializer serial = new XmlSerializer(Type.GetType(strType));
               
    serial.Serialize(writer, m_Child);
           
    writer.WriteEndElement();
        
    }
    }

    class Program
    {
       
    static void Main ( string[] args )
       
    {
           
    Parent parent = new Parent();
            
    parent.Child = new MyInterface();
           
    parent.Child.Name = "Mine";

           
    using (MemoryStream strm = new MemoryStream())
           
    {
               
    XmlSerializer serial = new XmlSerializer(parent.GetType());
               
    serial.Serialize(strm, parent);
           
    };
       
    }
    }

    In the above code notice that I create a new XML element to store the child.  I then store the underlying type so it can be recreated during deserialization.  A new serializer is then created to deserialize the actual type to the stream. 

    During deserialization it reads the child element, gets the underlying type, creates the instance of the interface, creates a new serializer to deserialize the data back and then assigns it to the field.

    The only problem I had with the above code was the fact that deserialization failed.  There is probably something missing like the schema definition or something.  But otherwise the class is properly serialized.

    Michael Taylor - 8/18/06

    Friday, August 18, 2006 2:15 PM
    Moderator

All replies

  • You can not serialize an interface.  The problem is that an interface is an opaque type.  There is no way for the serializer to know what to write out and more importantly what to create when it needs to serialize things back. 

    There are a variety of workarounds but ultimately it'll require that you handle serialization yourself.  Assuming that you are using XmlSerializer then your parent class should implement IXmlSerializable.  You can then implement the methods to read and write the fields.  For the interface you can get the actual underlying type and then create a new serializer that serializes the actual type to the XML stream.  You'd do the inverse to deserialize.

    public interface IMyInterface
    {
      
    string Name { get; set; }
    }

    public class MyInterface : IMyInterface
    {
      
    private string m_strName;
      
    public string Name
       {
          
    get { return m_strName ?? ""; }
          
    set { m_strName = value; }
       }
    }

    public class Parent : IXmlSerializable
    {
       
    public Parent ( ) { }

       
    private IMyInterface m_Child;
       
    public IMyInterface Child
        {
          
    get { return m_Child; }
          
    set { m_Child = value; }
        }

        System.Xml.Schema.
    XmlSchema IXmlSerializable.GetSchema ( )
        {
           
    return null;
        }

       
    void IXmlSerializable.ReadXml ( XmlReader reader )
        {
            reader.ReadStartElement(
    "Child");
               
    string strType = reader.GetAttribute("type");
               
    XmlSerializer serial = new XmlSerializer(Type.GetType(strType));
                m_Child = (
    IMyInterface)serial.Deserialize(reader);
            reader.ReadEndElement();
        }

        void IXmlSerializable.WriteXml ( XmlWriter writer )
       
    {
           
    writer.WriteStartElement("Child");
               
    string strType = m_Child.GetType().FullName;
               
    writer.WriteAttributeString("type", strType);
               
    XmlSerializer serial = new XmlSerializer(Type.GetType(strType));
               
    serial.Serialize(writer, m_Child);
           
    writer.WriteEndElement();
        
    }
    }

    class Program
    {
       
    static void Main ( string[] args )
       
    {
           
    Parent parent = new Parent();
            
    parent.Child = new MyInterface();
           
    parent.Child.Name = "Mine";

           
    using (MemoryStream strm = new MemoryStream())
           
    {
               
    XmlSerializer serial = new XmlSerializer(parent.GetType());
               
    serial.Serialize(strm, parent);
           
    };
       
    }
    }

    In the above code notice that I create a new XML element to store the child.  I then store the underlying type so it can be recreated during deserialization.  A new serializer is then created to deserialize the actual type to the stream. 

    During deserialization it reads the child element, gets the underlying type, creates the instance of the interface, creates a new serializer to deserialize the data back and then assigns it to the field.

    The only problem I had with the above code was the fact that deserialization failed.  There is probably something missing like the schema definition or something.  But otherwise the class is properly serialized.

    Michael Taylor - 8/18/06

    Friday, August 18, 2006 2:15 PM
    Moderator
  • Thanks Mike, exactly what I needed to know!

    Jeremy

    Friday, August 18, 2006 3:06 PM
  • Is there way to avoid the reduplication of serializing code. Meaning is there anyway to write code that only deals with serializing the interface property and letting the other properties be serialized automatically?

     

    Thanks,
    Dave

    Sunday, August 20, 2006 1:24 AM
  • Michael, your article about serializing interfaces is very helpful to me (I know it's been a while, but I hope you remember what you wrote...). In the end your said, that you had problems with the deserialization and that you assume a probelm in the schema definition. I do have the same problem: I can serialize well, but not deserialize. I wanted to ask you if you found an answer to what the problem could be. I can trying and trying but of no avail. Do you have any suggestions?

    Thanks,
    Toby
    Friday, January 11, 2008 4:14 PM
  • A few things which might help.  First, I would try and do a "reader.Read()" at the very beginning of the Deserialization code. 

     

    In the code above, it does a "GetType(...)" using the string type; however, it doesn't check to see if the resulting Type is not null.

     

    Can you provide what error you received during deserialization?

     

    Friday, January 11, 2008 6:10 PM
  • My XML code is fairly simple:

    <?xml version="1.0"?>
    <ODX>
      <Value>
        <string1>abc</string1>
      </Value>
    </ODX>

    To deserialize I have this code:

                ODX myodx;
                XmlSerializer testSer = new XmlSerializer(typeof(ODX));
                using (FileStream fStream = new FileStream(@"D:\test1.xml", FileMode.Open))
                {
                    myodx = (ODX)testSer.Deserialize(fStream);
                }

    The exception occurs in the last code line. My ReadXML code looks like this:

        void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
        {
    1        bool b = reader.Read();
    2        reader.ReadStartElement("Value");
    3        XmlSerializer serial = new XmlSerializer(typeof(ODXValue));
    4        object test = serial.Deserialize(reader);
    5        reader.ReadEndElement();
        }

    Through the schema, I generated the class ODXValue that represents the xml-tag "Value". Therefore, I guess I have to pass the type ODXValue to the serializer, or? BUT....if I do that, I get an exception in line 4 saying: "<string1 xmlns=''> was not expected." I am not sure on which node the reader is positioned in this situation. Any clues?
    Monday, January 14, 2008 2:47 PM
  • The design of XML serialization seems to put a load on the developer when dealing with interfaces.  That load then has a tendency to discourage proper component/interface based design of applications.

    I feel that there should be a declarative/annotation way to specify that interfaces should be included in export.  If I am only going to serialize to XML (say to transform to HTML) and I am never going to deserialize, then the serialization framework should support that use case.

    I also feel there should be declarative way to specify that the type information is included in the XML serialization, either by common full name or fully qualified name (ie. versioned name).   Deserialization can then make use of that attribute.   Make the type attribute a member of an MS namespace to avoid naming collisions.

    If these options still don't fill some need then we can always fall back to a custom serialization scheme as described here.  With the declarative options I describe, the need for custom serialization will drop cosiderably.
    Wednesday, August 27, 2008 4:17 PM
  • Rich, a declarative method of specifying what to do with interfaces is exactly what WCF provides. Have you researched it at all? There are some great new WCF resources available at http://msdn.microsoft.com/wcf, including endpoint.tv: The Total Noob's Guide to WCF - Lesson 1: My First WCF Service, which is a brief video (16 minutes).


    John Saunders | Use File->New Project to create Web Service Projects
    Thursday, August 28, 2008 12:00 AM
    Moderator
  • TaylorMichaelL's post is helpful on the subject of XmlSerialization with interfaces.  The error when deserializing took more research to fix.  While this is an old post it's on a useful subject.

    The bug with TaylorMichaelL's ReadXml has to do with the reader position when ReadXml is called (see link)(credit: Marc Gravell's post).  When ReadXml is called the reader position is right before the wrapping node <Parent> which must be read before <Child>.  At the end of ReadXml the ending element for the wrapping node must also be read </Parent>.  This is different than WriteXml which sets the position inside the wrapping node.  For WriteXml the writing <Child> and </Child> is correct.

    Another issue to overcome was that the XmlTextReader treats newline characters in the XML as nodes.  This means reader.Read() will stop at a newline character.  While you can change this behavior by setting xmlTextWriter.WhitespaceHandling = WhitespaceHandling.None, the behavior cannot be changed within ReadXml.  In short ReadXml should be prepared to handle whitespace.

     

    enough rant... below is working code heavily based on TaylorMichaelL's:

     

    public interface IMyInterface
    {
     string Name { get; set; }
    }
    
    public class MyInterface : IMyInterface
    {
     private string m_strName;
     public string Name
     {
      get { return m_strName ?? ""; }
      set { m_strName = value; }
     }
    }
    
    public class Parent : IXmlSerializable
    {
     private IMyInterface m_Child;
     public IMyInterface Child
     {
      get { return m_Child; }
      set { m_Child = value; }
     }
    
     System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
     {
      return null;
     }
    
     void IXmlSerializable.ReadXml(XmlReader reader)
     {
      string rootName = reader.Name;
      string fullTypeName = string.Empty;
      string shortTypeName = string.Empty;
      XmlSerializer serial = null;
    
      while (reader.Read())
      {
       if (reader.NodeType == XmlNodeType.Element)
       {
        if (reader.Name == "Child")
        {
         fullTypeName = reader.GetAttribute("type");
         serial = new XmlSerializer(Type.GetType(fullTypeName));
         shortTypeName = Type.GetType(fullTypeName).Name;
        }
        if (fullTypeName != string.Empty)
        {
         if (reader.Name == shortTypeName)
         {
          if (serial != null)
           m_Child = (IMyInterface) serial.Deserialize(reader);
         }
        }
       }
       if (reader.NodeType == XmlNodeType.EndElement)
       {
        if (reader.Name == rootName)
        {
         reader.ReadEndElement();
         break;
        }
       }
      }
     }
    
     void IXmlSerializable.WriteXml(XmlWriter writer)
     {
      writer.WriteStartElement("Child");
      string strType = m_Child.GetType().FullName;
      writer.WriteAttributeString("type", strType);
      XmlSerializer serial = new XmlSerializer(Type.GetType(strType));
      serial.Serialize(writer, m_Child);
      writer.WriteEndElement();
     }
    }
    
    class Program
    {
     static void Main(string[] args)
     {
      Parent parent = new Parent();
      parent.Child = new MyInterface();
      parent.Child.Name = "Mine";
    
      XmlSerializer serial = new XmlSerializer(parent.GetType());
    
      string xmlFile = "parent.xml";
      XmlTextWriter xt = new XmlTextWriter(xmlFile, Encoding.UTF8);
      xt.Formatting = Formatting.Indented;
    
      serial.Serialize(xt, parent);
      xt.Close();
    
      XmlTextReader xr = new XmlTextReader(xmlFile);
      Parent deserialized = serial.Deserialize(xr) as Parent;
      xr.Close();
    
      Console.WriteLine();
      if(deserialized == null)
      {
       Console.WriteLine("deserialization failed, object is null");
      }
      else
      {
       Console.WriteLine("deserialized parent.Child (IMyInterface)");
       Console.WriteLine(" Child.Name = " + deserialized.Child.Name);
      }
      Console.WriteLine();
      Console.WriteLine("press any key to continue.");
      Console.ReadKey(true);
     }
    }
    

     

    Note: you can omit "xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"" in your xml serialization by changing WriteXml to:

     

    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
     writer.WriteStartElement("Child");
     string strType = m_Child.GetType().FullName;
     writer.WriteAttributeString("type", strType);
     XmlSerializer serial = new XmlSerializer(Type.GetType(strType));
    
     XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
     ns.Add(string.Empty, string.Empty);
     serial.Serialize(writer, m_Child, ns);
    
     //serial.Serialize(writer, m_Child);
     writer.WriteEndElement();
    }

     

    • Edited by McDon Wednesday, June 09, 2010 2:36 PM typos
    Tuesday, June 08, 2010 10:23 PM
  • This works only if "Child" is the only member of the "Parent" class. Well, it doesn't fail - other members (if any) are simply not serialized.
    Saturday, July 10, 2010 3:19 PM
  • An alternative...

    I have  two very similar XSD files and used the xsd.exe tool to generate classes. I decided to customize the classes a bit, so  that I could pass both to the same object structures -- hence I manually added an interface to them. the schemas have multiple nested tables: i.e. one XML node contains an array of nodes that in turn contains an array of nodes. All three levels needed to be extracted to interfaces.

    Simply changing the serialized array node to return the interface causes the problem described above, which did not surprise me (I would have been surprised had it worked, since how could the compiler know which concrete class to utilize?). So, instead I created method calls in the interface for retrieving the sub-array of nodes. Methods are not serialized, so it compiles cleanly.

    Lesson: if you want to mix XML serialization and interfaces, put methods into your interfaces instead of properties. The concrete method can simply return the real property.

    Edit: you can also provide an alternative property, only used by the interface, and mark it with:

    [System.Xml.Serialization.XmlIgnore]

     

    Thursday, August 19, 2010 3:59 PM
  • Rather than customize the generated classes, you can use the Partial Class feature of C#. For instance, if the generated class is named "Namespace.NeedsInterface", then create your own class part in a file named, for instance, "NeedsInterface.Interfaces.cs":

    public partial class NeedsInterface : IInterface1, IInterface2
    {
    }
    
    

    This forces the complete class to implement those interfaces.

    Of course, you then have to actually implement the interfaces, unless the generated class already does so without knowing about it.


    John Saunders
    WCF is Web Services. They are not two separate things.
    Use WCF for All New Web Service Development, instead of legacy ASMX or obsolete WSE
    Use File->New Project to create Web Service Projects
    Thursday, August 19, 2010 7:31 PM
    Moderator
  • This is a good idea - but most people serialise in order to store state i.e. record the current property values. Interfaces are included as a way of holding references to a family of classes - again these would contain property values that presumabley we want to retain as part of the serialisation process.

    If these values are not stored at the point of serialisation how can the method return the required value following deserialisation? Do you have a way of storing these values?


    Thanks
    Wednesday, January 19, 2011 8:57 AM
  • You cannot serialize interfaces. You can only serialize a concrete type that may implement the interface.

    Interfaces have only behavior. They have no state. Even an interface which includes a property has no state. The concrete class that implements the interface may have state.

    In fact, that state may not simply be the property value. For instance, with an interface that describes properties Height, Width, and Area, how do you know which of the three values are required by the class? Any one of them may be computed given the other two.

    Finally, how would the deserializing code know which class to instantiate? There may be any number of classes implementing the interface.


    John Saunders
    WCF is Web Services. They are not two separate things.
    Use WCF for All New Web Service Development, instead of legacy ASMX or obsolete WSE
    Use File->New Project to create Web Service Projects
    • Proposed as answer by Baldrick Wednesday, January 19, 2011 8:45 PM
    Wednesday, January 19, 2011 7:38 PM
    Moderator
  • John - I understand this - but sfuqua was suggesting you could use methods within a serialised class that included interfaces to return the required values. This way the interface properties could be ignored. My question was how do you get the concrete class values (referenced by the interface property) to be stored as part of the serialisation.

    I have been giving this some thought. Having looked at the example here and a few other examples on line I have tried to create a generic implementation of IXmlSerializable that does NOT require the Read and Write methods to be updated each time the class is changed (and so it can be reused in any class).

    I have used reflection before for a number of practical applications including a basic plugin architecture but my experience is limited. This code seems to work but could do with improvement. Hopefully it sparks some thought on this issue. This code should handle all value types, interfaces and concrete classes. All comments welcome!

     

    public void ReadXml(XmlReader reader)
    {
      var rootName = reader.Name;
      var props = GetType().GetProperties();

      //Move to first node after root
      reader.Read();

      while (!reader.EOF)
      {
        //Check to see if a property exists within our class that has the same name as XML node
        var prop = props.SingleOrDefault(p => p.Name == reader.Name);

       if (prop != null && reader.NodeType != XmlNodeType.EndElement)
      {
         if (prop.PropertyType.IsInterface)
        {
          //Get the type string of original class instance from attribute in XML
         
    var fullTypeName = reader.GetAttribute("type");
         
    if (fullTypeName != null)
          {
            
    //Create serialiser for class instance taken from type attribute in XML file
             var serial = new XmlSerializer(Type.GetType(fullTypeName));
             var shortName = Type.GetType(fullTypeName).Name;
             //Move onto node that represents the class implementation of interface
             reader.Read();
             if (reader.NodeType == XmlNodeType.Element && reader.Name == shortName)
                prop.SetValue(
    this, serial.Deserialize(reader), null);
           }
         }
         else if (prop.PropertyType.IsValueType)
        {
           //Think this handles all data types  
         
    prop.SetValue(this, reader.ReadElementContentAs(prop.PropertyType, null), null);

     

         }
        
    else if (prop.PropertyType.IsClass)
        
    {
           
    //Read entire class node using normal class serialisation
          
    //We have to specify the root name because by default the class type name is used - not the propery name

     

            var serial = new XmlSerializer(prop.PropertyType, new XmlRootAttribute(prop.Name));
           
    prop.SetValue(this, serial.Deserialize(reader), null);
           
    reader.Read();
          
    }
          
    //Maybe some more type handlers?
    }

    if (reader.NodeType == XmlNodeType.EndElement)
    {
       
    if (reader.Name == rootName)
       
    {
          
    reader.ReadEndElement();
          
    break;
        
    }
       
    reader.Read();
    }
    }
    }

     

    public void WriteXml(XmlWriter writer)
    {
      
    //Create an empty namespace - this removes namespaces from the serialised XML
      
    var ns = new XmlSerializerNamespaces();
       ns.Add(
    "", "");

     

       var props = GetType().GetProperties();
      
    //Loop through each property and handle the serialisation to the XML as appropriate
      
    foreach (var prop in props)
      
    {
         
    if (prop.PropertyType.IsValueType)
          {
            
    if (prop.PropertyType == typeof(Guid))
             {
                 writer.WriteElementString(prop.Name, prop.GetValue(
    this, null).ToString());
             }
            
    else
            
    {
                 writer.WriteStartElement(prop.Name);
                 writer.WriteValue(prop.GetValue(
    this, null));
                 writer.WriteEndElement();
              }
            }
          
    else if(prop.PropertyType.IsInterface)
          {
             
    //Create parent node for our class using interface property name
             
    writer.WriteStartElement(prop.Name);
             
    //Get class instance referenced by our interface property
             
    var obj = prop.GetValue(this, null);
             
    var strType = obj.GetType().FullName;
             
    //Store full type name of class in type attribute
             
    writer.WriteAttributeString("type", strType);
             
    //Serialise class as normal - uses class type name as root node by default
             
    var serial = new XmlSerializer(Type.GetType(strType));
              serial.Serialize(writer, obj, ns);
              writer.WriteEndElement();
            }
           
    else if(prop.PropertyType.IsClass)
           {
              
    //Serialise class normally - need to use property name for the root element
              
    //so we can match the class to property when we deserialise
              
    var serial = new XmlSerializer(prop.PropertyType, new XmlRootAttribute(prop.Name));
               serial.Serialize(writer, prop.GetValue(
    this,null), ns);
            }

             //Maybe some more type handlers??
        }
    }


    Thanks

     

     

     

     

     

    Wednesday, January 19, 2011 9:14 PM
  • What about on the client side? And what if the client is not running .NET or not running your code.

    You should note the date of the original question. The problem hasn't been solved in four years. You are not going to be the one to solve it.


    John Saunders
    WCF is Web Services. They are not two separate things.
    Use WCF for All New Web Service Development, instead of legacy ASMX or obsolete WSE
    Use File->New Project to create Web Service Projects
    Thursday, January 20, 2011 4:31 AM
    Moderator