Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize interface typed member

I have a class that has a property that is defined as interface. Users of my class can assign to this property any class implementation that implements the interface. I want to be able to load this class state from a textual file on disk. Users should be able to manually modify the xml file, in order to control the application's operation.

If I try to serialize my class, it tells me I cannot serialize an interface. I understand that the serializer has no knowledge about the structure of the property's class, knowing only that it implements an interface.

I would have expected it to call GetType on the member, and reflect into the structure of the actual class. Is there a way to achieve this? Is there another way to implement my requirement?

Edit: Clarifying my intentions: Lets say I have this class:

class Car
{
IEngine engine
}
class ElectricEngine : IEngine 
{
int batteryPrecentageLeft;
}
class InternalCombustionEngine : IEngine 
{
int gasLitersLeft;
}

and the class user has a class with

Car myCar = new Car();
myCar.Engine = new ElectricEngine() {batteryPrecentageLeft= 70};

When I serialize the class myCar, I expect the the xml to resemble this:

<Car>
<Engine>
<ElectricEngine>
<batteryPrecentageLeft>70</batteryPrecentageLeft>
</ElectricEngine>
<Engine>
</Car>
like image 576
itaysk Avatar asked Apr 20 '12 19:04

itaysk


3 Answers

You can mark the property as do-not-include.

There is a deeper problem though: Serialization can only capture simple 'state', not behaviour. Your class is not of the serializable kind. What 'value' do you expect the property to have after deserialization? null is the only option.

The proper workaround would be to think about What should actually be save and use a DTO for that part.


The following model can be serialized:

public class BaseEngine { }

[XmlInclude(typeof(InternalCombustionEngine))]
[XmlInclude(typeof(ElectricEngine))]
public class Car
{      
    public BaseEngine Engine { get; set; }
}
like image 159
Henk Holterman Avatar answered Nov 19 '22 13:11

Henk Holterman


Maybe you could use a base class instead of an interface and serialize that.

Update

I realized that using a base class was not really an option for you.

The best solution would probably be to do a workaround with a DTO like Henk Holterman stated.

But if you really want a solution for your question I think you would have to create your own custom serializer, but I would not recommend that because you would end up with a lot of bugs to sort out.

Here is a example for a custom serializer, keep in mind that this example will need some work to be use full in a real application.

At least two things has to be added for this to work for more than just an example:

  1. Exception handling
  2. Casting or converting the xml element value to correct type on the line anyThingProperty.SetValue(obj, propertyElement.Value, null);
[TestClass]
public class SerializableInterfaceTest
{
    [TestMethod]
    public void TestMethod1()
    {
        string serialize = AnyThingSerializer.Serialize(
            new SerializableClass {Name = "test", Description = "test1", 
                AnyThing = new Animal {Name = "test", Color = "test1"}});
        Console.WriteLine(serialize);
        object obj = AnyThingSerializer.Deserialize(serialize);
    }
}

public sealed class SerializableClass
{
    public string Name { get; set; }
    public string Description { get; set; }

    [AnyThingSerializer]
    public object AnyThing { get; set; }
}

public static class AnyThingSerializer
{
    public static string Serialize(object obj)
    {
        Type type = obj.GetType();
        var stringBuilder = new StringBuilder();
        var serializer = new XmlSerializer(type);
        serializer.Serialize(new StringWriter(stringBuilder), obj);
        XDocument doc = XDocument.Load(new StringReader(stringBuilder.ToString()));
        foreach (XElement xElement in SerializeAnyThing(obj))
        {
            doc.Descendants().First().Add(xElement);
        }
        return doc.ToString();
    }

    public static object Deserialize(string xml)
    {
        var serializer = new XmlSerializer(typeof (T));
        object obj = serializer.Deserialize(new StringReader(xml));
        XDocument doc = XDocument.Load(new StringReader(xml));
        DeserializeAnyThing(obj, doc.Descendants().OfType().First());
        return obj;
    }

    private static void DeserializeAnyThing(object obj, XElement element)
    {
        IEnumerable anyThingProperties = obj.GetType()
            .GetProperties().Where(p => p.GetCustomAttributes(true)
                .FirstOrDefault(a => a.GetType() == 
                    typeof (AnyThingSerializerAttribute)) != null);
        foreach (PropertyInfo anyThingProperty in anyThingProperties)
        {
            XElement propertyElement = element.Descendants().FirstOrDefault(e => 
                e.Name == anyThingProperty.Name && e.Attribute("type") != null);
            if (propertyElement == null) continue;
            Type type = Type.GetType(propertyElement.Attribute("type").Value);
            if (IsSimpleType(type))
            {
                anyThingProperty.SetValue(obj, propertyElement.Value, null);
            }
            else
            {
                object childObject = Activator.CreateInstance(type);
                DeserializeAnyThing(childObject, propertyElement);
                anyThingProperty.SetValue(obj, childObject, null);
            }
        }
    }

    private static List SerializeAnyThing(object obj)
    {
        var doc = new List();
        IEnumerable anyThingProperties = 
            obj.GetType().GetProperties().Where(p => 
                p.GetCustomAttributes(true).FirstOrDefault(a => 
                    a.GetType() == typeof (AnyThingSerializerAttribute)) != null);
        foreach (PropertyInfo anyThingProperty in anyThingProperties)
        {
            doc.Add(CreateXml(anyThingProperty.Name, 
                anyThingProperty.GetValue(obj, null)));
        }
        return doc;
    }

    private static XElement CreateXml(string name, object obj)
    {
        var xElement = new XElement(name);
        Type type = obj.GetType();
        xElement.Add(new XAttribute("type", type.AssemblyQualifiedName));
        foreach (PropertyInfo propertyInfo in type.GetProperties())
        {
            object value = propertyInfo.GetValue(obj, null);
            if (IsSimpleType(propertyInfo.PropertyType))
            {
                xElement.Add(new XElement(propertyInfo.Name, value.ToString()));
            }
            else
            {
                xElement.Add(CreateXml(propertyInfo.Name, value));
            }
        }
        return xElement;
    }

    private static bool IsSimpleType(Type type)
    {
        return type.IsPrimitive || type == typeof (string);
    }
}

public class AnyThingSerializerAttribute : XmlIgnoreAttribute
{
}
like image 25
Jens Granlund Avatar answered Nov 19 '22 15:11

Jens Granlund


Based on @Jens solution I have created a serializer that does what I need. Thanks Jen. Here is the code:

public class RuntimeXmlSerializerAttribute : XmlIgnoreAttribute { }

public class RuntimeXmlSerializer
{
    private Type m_type;
    private XmlSerializer m_regularXmlSerializer;

    private const string k_FullClassNameAttributeName = "FullAssemblyQualifiedTypeName";

    public RuntimeXmlSerializer(Type i_subjectType)
    {
        this.m_type = i_subjectType;
        this.m_regularXmlSerializer = new XmlSerializer(this.m_type);
    }

    public void Serialize(object i_objectToSerialize, Stream i_streamToSerializeTo)
    {
        StringWriter sw = new StringWriter();
        this.m_regularXmlSerializer.Serialize(sw, i_objectToSerialize);
        XDocument objectXml = XDocument.Parse(sw.ToString());
        sw.Dispose();
        SerializeExtra(i_objectToSerialize,objectXml);
        string res = objectXml.ToString();
        byte[] bytesToWrite = Encoding.UTF8.GetBytes(res);
        i_streamToSerializeTo.Write(bytesToWrite, 0, bytesToWrite.Length);
    }

    public object Deserialize(Stream i_streamToSerializeFrom)
    {
        string xmlContents = new StreamReader(i_streamToSerializeFrom).ReadToEnd();
        StringReader sr;
        sr = new StringReader(xmlContents);
        object res = this.m_regularXmlSerializer.Deserialize(sr);
        sr.Dispose();
        sr = new StringReader(xmlContents);
        XDocument doc = XDocument.Load(sr);
        sr.Dispose();
        deserializeExtra(res, doc);
        return res;
    }

    private void deserializeExtra(object i_desirializedObject, XDocument i_xmlToDeserializeFrom)
    {
        IEnumerable propertiesToDeserialize = i_desirializedObject.GetType()
            .GetProperties().Where(p => p.GetCustomAttributes(true)
                .FirstOrDefault(a => a.GetType() ==
                    typeof(RuntimeXmlSerializerAttribute)) != null);
        foreach (PropertyInfo prop in propertiesToDeserialize)
        {
            XElement propertyXml = i_xmlToDeserializeFrom.Descendants().FirstOrDefault(e =>
                e.Name == prop.Name);
            if (propertyXml == null) continue;
            XElement propertyValueXml = propertyXml.Descendants().FirstOrDefault();
            Type type = Type.GetType(propertyValueXml.Attribute(k_FullClassNameAttributeName).Value.ToString());
            XmlSerializer srl = new XmlSerializer(type);
            object deserializedObject = srl.Deserialize(propertyValueXml.CreateReader());
            prop.SetValue(i_desirializedObject, deserializedObject, null);
        }
    }

    private void SerializeExtra(object objectToSerialize, XDocument xmlToSerializeTo)
    {
        IEnumerable propertiesToSerialize =
            objectToSerialize.GetType().GetProperties().Where(p =>
                p.GetCustomAttributes(true).FirstOrDefault(a =>
                    a.GetType() == typeof(RuntimeXmlSerializerAttribute)) != null);
        foreach (PropertyInfo prop in propertiesToSerialize)
        {
            XElement serializedProperty = new XElement(prop.Name);
            serializedProperty.AddFirst(serializeObjectAtRuntime(prop.GetValue(objectToSerialize, null)));
            xmlToSerializeTo.Descendants().First().Add(serializedProperty); //TODO
        }
    }

    private XElement serializeObjectAtRuntime(object i_objectToSerialize)
    {
        Type t = i_objectToSerialize.GetType();
        XmlSerializer srl = new XmlSerializer(t);
        StringWriter sw = new StringWriter();
        srl.Serialize(sw, i_objectToSerialize);
        XElement res = XElement.Parse(sw.ToString());
        sw.Dispose();
        XAttribute fullClassNameAttribute = new XAttribute(k_FullClassNameAttributeName, t.AssemblyQualifiedName);
        res.Add(fullClassNameAttribute);

        return res;
    }
}
like image 3
itaysk Avatar answered Nov 19 '22 14:11

itaysk