Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Xml Serialization of Unknown Type

I'm attempting to deserialize a custom class via the XmlSerializer and having a few problems, in the fact that I don't know the type that I'm going to be deserializing (it's pluggable) and I'm having difficulty determining it.

I found this post which looks similar but can't quite get it to work with my approach because I need to deserialize an interface which is XmlSerializable.

What I've currently got is of the form. Note that I expect and need to be able to handle both class A and class B to be implemented via a plugin. So if I can avoid using the IXmlSerializable (which I don't think I can) then that would be great.

The ReadXml for A is what I'm stuck on. However if there are other changes that I can make to improve the system then I'm happy to do so.

public class A : IXmlSerializable
{
   public IB MyB { get; set;}

   public void ReadXml(System.Xml.XmlReader reader)
   {
      // deserialize other member attributes

      SeekElement(reader, "MyB");
      string typeName = reader.GetAttribute("Type");

      // Somehow need to the type based on the typename. From potentially 
      //an external assembly. Is it possible to use the extra types passed 
      //into an XMlSerializer Constructor???
      Type bType = ???

      // Somehow then need to deserialize B's Members
      // Deserialize X
      // Deserialize Y
   }

   public void WriteXml(System.Xml.XmlWriter writer)
   {
      // serialize other members as attributes

      writer.WriteStartElement("MyB");
      writer.WriteAttributeString("Type", this.MyB.GetType().ToString());
      this.MyB.WriteXml(writer);
      writer.WriteEndElement();
   }

   private void SeekElement(XmlReader reader, string elementName)
   {
      ReaderToNextNode(reader);
      while (reader.Name != elementName)
      {
         ReaderToNextNode(reader);
      }
   }

   private void ReaderToNextNode(XmlReader reader)
   {
      reader.Read();
      while (reader.NodeType == XmlNodeType.Whitespace)
      {
         reader.Read();
      }
   }
}

public interface IB : IXmlSerializable
{
}

public class B : IB
{

     public void ReadXml(XmlReader reader)
     {
         this.X = Convert.ToDouble(reader.GetAttribute("x"));
         this.Y = Convert.ToDouble(reader.GetAttribute("y"));
     }

   public void WriteXml(XmlWriter writer)
   {
      writer.WriteAttributeString("x", this.X.ToString());
      writer.WriteAttributeString("y", this.Y.ToString());
   }
}

NOTE : Updated as I realised B was supposed to use interface IB. Sorry for slightly wrong question.

like image 803
Ian Avatar asked Mar 13 '26 11:03

Ian


2 Answers

To create an instance from a string, use one of the overloads of Activator.CreateInstance. To just get a type with that name, use Type.GetType.

like image 89
John Saunders Avatar answered Mar 16 '26 05:03

John Saunders


I don't think you need to implement IXmlSerializable...

Since you don't know the actual types before runtime, you can dynamically add attribute overrides to the XmlSerializer. You just need to know the list of types that inherit from A. For instance, if you use A as a property of another class :

public class SomeClass
{
    public A SomeProperty { get; set; }
}

You can dynamically apply XmlElementAttributes for each derived type to that property :

XmlAttributes attr = new XmlAttributes();
var candidateTypes = from t in AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
                     where typeof(A).IsAssignableFrom(t) && !t.IsAbstract
                     select t;
foreach(Type t in candidateTypes)
{
    attr.XmlElements.Add(new XmlElementAttribute(t.Name, t));
}

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(SomeClass), "SomeProperty", attr);

XmlSerializer xs = new XmlSerializer(typeof(SomeClass), overrides);
...

This is just a very basic example, but it shows how to apply XML serialization attributes at runtime when you can't do it statically.

like image 44
Thomas Levesque Avatar answered Mar 16 '26 03:03

Thomas Levesque