Tuesday, November 30, 2010

Serialize to XML made a little easier

COVERAGE: XML Serialization, C#.
While digging, breaking and smashing around some code (aka refactoring), I found some redundant code that serialized and deserialized objects to files at various places. With a little effort a centralized helper class was created.
Serialization can be useful in many ways. I prefer to use it to save stuff like metadata using an object which has compile-time support, is easier to maintain* and delivers more readable code.

*) Due to compile-time support, but changes to fields could have breaking effects on existing files. Consider using XmlElementAttribute("Name") attribute on target properties for fixed element names.

Usage

Here is a simplyfied example to show how it works:
public static void SerializationExample()
{
    var tagData = new MovieTagData("Science fiction", "Scifi", "Monkeys", "Battle");
    var path = "C:\Movies\Planet of the Apes.avi.xml";
    ObjectXmlSerializer.Serialize(tagData , path);
}

public static void DeserializationExample()
{
    var path = "C:\Movies\Planet of the Apes.avi.xml";
    var tagData = ObjectXmlSerializer.Deserialize<MovieTagData>(path);

    foreach(var movieTag in tagData) // Further processing example
    ... 

}
As you can see, a few lines can dump or restore an object.

Source code

using System;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using CuttingEdge.Conditions;

///  <summary> Provides methods to serialize 
///  and deserialize objects easily on the the disk. </summary>
public static class ObjectXmlSerializer
{
    /// <summary> Serializes the specified serializeble object. </summary>
    /// <param name="serializebleObject">The serializeble object.</param>
    /// <param name="path">The path.</param>
    public static void Serialize(object serializebleObject, string path)
    {
        Condition.Requires(serializebleObject, "serializebleObject").IsNotNull();
        Condition.Requires(path, "path").IsNotNullOrEmpty();

        ValidateObjectIsSerializable(serializebleObject);

        WriteObjectToDisk(serializebleObject, path);
    }

    private static void WriteObjectToDisk(object serializebleObject, string path)
    {
        var serializableType = new XmlSerializer(serializebleObject.GetType());
        using(var fileWriter = new StreamWriter(path))
        {
            serializableType.Serialize(fileWriter, serializebleObject);
            fileWriter.Close();
        }
    }

    private static void ValidateObjectIsSerializable(object serializebleObject)
    {
        var expectedAttributes = serializebleObject.GetType()
            .GetCustomAttributes(typeof(SerializableAttribute), true);

        if(expectedAttributes.Count() == 0)
        {
            throw new InvalidOperationException(
                "Object must be decorated with a 'SerializableAttribute' attribute.");
        }
    }

    /// <summary> Deserializes the specified path. </summary>
    /// <typeparam name="T"> Typeof serializable object </typeparam>
    /// <param name="path">The path.</param>
    /// <returns> An instance according to the specified type of object. </returns>
    public static T Deserialize<T>(string path) 
    {
        Condition.Requires(path, "path").IsNotNullOrEmpty();
    
        using(var fileReader = new StreamReader(path))
        {
            var objectDeserializer = new XmlSerializer(typeof(T));

            return (T)objectDeserializer.Deserialize(fileReader);
        }
    }
}

Conclusion

If you are new to serialization you may need to know that private or readonly fields cannot be (de)serialized. The XmlSerializer can only set public writable fields. Another notable thing is serializing large fields like byte arrays. For example, if you want to serialize a file with metadata in a object. Consider this carefully, deserializing will load the whole file (byte array) into the memory. This could be performance / memory implication when you just want to browse through files.

No comments:

Post a Comment