The Code Slinger

December 17, 2007

Custom XML Serialization with LINQ

Filed under: C#,LINQ,Reflection,XLINQ — Pete @ 11:24 pm

Wouldn’t it be nice if you could automatically serialize into XML not only the structure of any class in your business framework, but the actual values at runtime of each instance? This could be used for additional logging to an offsite service, re-hydrating objects at a later time, or simply a form of custom serialization without all the overhead the native XMLSerializer has built into it.

Previously I had this type of code implemented as an interface, so that each object could serialize itself if necessary.  This was cumbersome and while in theory one might want a different XML structure per business object, typically in my uses I found that each object just needed to output the direct data it held.   So using the new 3.5 framework features like LINQ & XLINQ, let’s take a look at what a custom serializer might look like (full source is posted at the end of the article as usual).

public static class Serializer<T> where T: class, new()

{

    private static Dictionary<Type, PropertyInfo[]> props =

        new Dictionary<Type, PropertyInfo[]>();

}

First, our class will be a generic implementation, so that we can pass in any of our business entity objects.  Inside we have a static member which is simply a container for keeping track of our PropertyInfo arrays based on class type.  This keeps us, over time, from having to duplicate reflection calls unnecessarily.

public static string Serialize(T instance)

{

    Type t = typeof(T);

    PropertyInfo[] pis = null;

    lock (props)

    {

        if (!props.TryGetValue(t, out pis))

        {

            PropertyInfo[] ps = t.GetProperties(

                BindingFlags.Public |

                BindingFlags.GetProperty |

                BindingFlags.Instance);

            props.Add(t, ps);

        }

        pis = props[t];

    }

 

    XElement xd = new XElement(t.Name);

    pis.ForEach(p =>

    {

        object val = null;

        PropertyCaller<T>.GenGetter g =

            PropertyCaller<T>.CreateGetMethod(p);

        val = g(instance);

        if (val == null)

            val = “null”;

        XAttribute xa = new XAttribute(p.Name, val);

        xd.Add(xa);

    });

 

    return xd.ToString();

}

Here our Serialize method takes as input an instance of the type we are serializing. It converts the Type data, and then looks in our container to see if we have already serialized one of these previously.  If not, it gets the necessary PropertyInfo data from the Type and inserts it for future use into our container. 

Next we use the XElement class to create a new xml node named after our class type.  You could use any data you want from the class, I just chose to use the Name property.  Now, we use the extension method ForEach<T> of the PropertyInfo array to execute a lambda expression.  This expression basically just uses the PropertyCaller<T> class we created previously to get the delegate “getter” for each property on the class.  Calling this delegate will retrieve the value of the property for the instance we are serializing.  Then I simply use the name of the property and it’s value to create an XAttribute to decorate the XElement for our object.

The return value of the Serialize method is simply the string output of our XElement object in XML.

Now let’s look at how to take that string XML and turn it back into our object.

public static T Deserialize(XElement xd)

{

    Type t = typeof(T);

    PropertyInfo[] pis = null;

    lock (props)

    {

        if (!props.TryGetValue(t, out pis))

        {

            PropertyInfo[] ps = t.GetProperties(

                BindingFlags.Public |

                BindingFlags.SetProperty |

                BindingFlags.Instance);

            props.Add(t, ps);

        }

        pis = props[t];

    }

    IEnumerable<XAttribute> ix = xd.Attributes();

 

    T lilt = new T();

    pis.ForEach(p =>

    {

        string propname = p.Name;

        var att = (from xat in ix

                   where xat.Name.LocalName == p.Name

                   select xat).FirstOrDefault();

        if(att != null)

        {

            PropertyCaller<T>.GenSetter s =

                PropertyCaller<T>.CreateSetMethod(p);

            object val = Converter(p.PropertyType, att.Value);

            s(lilt, val);

        }

    });

    return lilt;

}

Our Deserialize method takes an XElement instance which represents the data for a particular instance of T.   You could also take plain string XML and load it into the XElement if you wanted, via the XElement.Parse() method.

Again, we get our PropertyInfo array so we know how to match up the XAttributes, then again using the ForEach<T> extension method, we provide a lambda expression (anonymous method) implementation which determines which XAttribute of the XElement node matches up with each property name.  Notice the LINQ query on the results of the Attributes() method off of the XElement object.  Since the results are of type IEnumerable<T>, we know that we can query them using LINQ.   In this case we simply find the FirstOrDefault (null if one doesn’t exist) value in the query in which the LocalName of the XAttribute matches our property name. 

Now if we have a matching attribute for the property, we again use our PropertyCaller<T> implementation to get the delegate “setter” for the property.  Calling this delegate with the value of our attribute, we are able to set the value of our object for the property specified.

The Converter method is simply a helper method to allow us to properly convert the string representation of the value stored in the XML into an actual object type based on the property’s type. 

public static object Converter(Type t, string value)

{

    object retval = null;

    string checkType = string.Empty;

    bool valueset = false;

    if (t.IsGenericType)

    {

        checkType = t.GetGenericArguments()[0].FullName;

        if (value == “null”)

        {

            valueset = true;

            retval = null;

        }

    }

    else

        checkType = t.FullName;

 

    if (!valueset)

    {

        switch (t.FullName)

        {

            case “System.Int32”:

                retval =

                    new Int32Converter().ConvertFrom(value);

                break;

            case “System.Double”:

                retval =

                    new DoubleConverter().ConvertFrom(value);

                break;

            case “System.Decimal”:

                retval =

                    new DecimalConverter().ConvertFrom(value);

                break;

            case “System.Boolean”:

                retval =

                    new BooleanConverter().ConvertFrom(value);

                break;

            case “System.DateTime”:

                retval =

                    new DateTimeConverter().ConvertFrom(value);

                break;

            default:

                retval = value;

                break;

        }

    }

    return retval;

}

The only real gotcha with this is that you have to implement a conversion routine (I use TypeConverters) for each type you are storing in your classes.  Typically this is limited to a relatively small set of types (mostly value types).  This particular implementation handles generic “nullable” versions of each value type listed.  In the XML serialization routine, null values are explicitly spelled out as “null”. 

Anyway, the sample project should give you a pretty clear understanding of how to put all this together.  Hopefully this has shown how to not only accomplish a potentially system-wide task with relatively little code, but how to build upon the dynamic method invocation code we put together previously in a real-world scenario.

Til next time…keep on slangin!

————————————————

Example Project Source

Blog at WordPress.com.