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

Advertisements

5 Comments »

  1. Very useful code!

    I think it is important to leave a note in the code that it has been optimized for performance.

    I downloaded the code, but ClassFactory…CreateClass confused me for a few minutes. My first thought was “what is wrong with classic new Customer()”.

    Cheers

    Comment by Rickard Nilsson — January 4, 2008 @ 7:22 am | Reply

  2. Rickard…good point. I used the class factory to mimic how this would probably be used in the real world…i.e. you don’t know where you got your object from, just that you have one that needs serialization.

    You could just as easily test this with directly instantiated objects like new Customer();

    Comment by Pete — January 7, 2008 @ 1:14 pm | Reply

  3. Improve converter performance by sharing TypeConverter references.
    “Never access a type converter directly. Instead, access the appropriate converter by using TypeDescriptor.” -Msdn
    http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter.aspx

    Comment by Rana Ian — May 26, 2008 @ 10:58 pm | Reply

  4. Useful code…

    I want to use this code into my code.

    I have to create XElement from user defined object which contains system defined types and user defined types.

    Right now what i am doing is explicitly creating XElement for each member variable and then appending to main XElement.(http://forums.asp.net/t/1384396.aspx)

    The above code is for system defined types. Can we make generic code for class having system defined and user defined type(which recursively contains system defined types and user defined types)?

    Any help is appriciated…

    Comment by sadhu — March 9, 2009 @ 12:40 pm | Reply

  5. What about this one

    Private Shared ReadOnly Property SurveyInfoPath() As String
    Get
    Return System.Windows.Forms.Application.StartupPath + “\SurveyInfo.xml”
    End Get
    End Property

    Private Shared Function AddChildNode(ByVal parentNode As XmlNode, ByVal TagName As String, Optional ByVal Value As Object = Nothing) As XmlNode
    Dim childNode As XmlNode = parentNode.OwnerDocument.CreateElement(TagName)
    childNode.InnerText = Convert.ToString(Value)
    Return parentNode.AppendChild(childNode)
    End Function

    Private Shared Sub ReflectProperties(ByVal parentNode As XmlNode, ByVal value As Object)
    For Each propInfo As System.Reflection.PropertyInfo In value.GetType().GetProperties
    Dim type As System.Type = propInfo.PropertyType
    ‘If type Is GetType(Integer) OrElse type Is GetType(String) OrElse type Is GetType(DateTime) Then
    If type.IsSerializable Then
    AddChildNode(parentNode, propInfo.Name, propInfo.GetValue(value, Nothing))
    End If
    Next
    End Sub

    Public Shared Sub WriteSurveyInfo(ByVal surveyInfo As SurveyMaster)

    Dim xDoc As New XmlDocument()
    Try
    xDoc.CreateXmlDeclaration(“1.0”, Nothing, Nothing)
    Dim rootNode As XmlNode = xDoc.CreateElement(“SurveyMaster”)
    Dim childNode As XmlNode
    ‘AddChildNode(rootNode, “SurveyID”, surveyInfo.SurveyID)
    ‘AddChildNode(rootNode, “SurveyName”, surveyInfo.SurveyName)
    ReflectProperties(rootNode, surveyInfo)
    For Each surveyQuestion In surveyInfo.SurveyQuestionDetails
    childNode = AddChildNode(rootNode, “SurveyQuestionDetails”)
    ReflectProperties(childNode, surveyQuestion)
    Dim questionMasterNode = AddChildNode(childNode, “QuestionMaster”)
    ReflectProperties(questionMasterNode, surveyQuestion.QuestionMaster)
    For Each questionText In surveyQuestion.QuestionMaster.QuestionTextMasters
    Dim questionTextNode = AddChildNode(questionMasterNode, “QuestionTextMasters”)
    ReflectProperties(questionTextNode, questionText)
    Next
    Next

    xDoc.AppendChild(rootNode)
    xDoc.Save(SurveyInfoPath)
    Catch ex As Exception
    MessageBox.Show(ex.ToString)
    End Try
    End Sub

    Comment by Divyesh — March 18, 2009 @ 3:21 am | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: