I have recently had a need to serialize objects in .NET down to a byte array to send to another system for a project that I was working on, so I thought I would share some of the code.

I have written about serializing POCO objects into XML before on this blog.

In the rest of this post I will show a simple implementation of a class called ObjectSerialize that adds a set of extension methods onto the base object class.

ObjectSerialize Class
ObjectSerialize Class

The ObjectSerialize class contains 2 public methods, Serialize and DeSerialize. These methods will be available as extension methods on the base object method in .NET. There are also 2 private methods (Compress and Decompress) that apply GZip compression to the object being serialized to ensure the byte arrays are as compact as possible.

Lets take a look at the Serialize method.

public static byte[] Serialize(this Object obj)
{
    if (obj == null)
    {
        return null;
    }

    using (var memoryStream = new MemoryStream())
    {
        var binaryFormatter = new BinaryFormatter();
        binaryFormatter.Serialize(memoryStream, obj);
        var compressed = Compress(memoryStream.ToArray());

        return compressed;
    }
}

This method takes an object and then serialises the object into a MemoryStream using a BinaryFormatter. The MemoryStream is then run thorough the compress method to apply the GZip compression. The resulting byte array is then returned.

public static Object DeSerialize(this byte[] arrBytes)
{
    using (var memoryStream = new MemoryStream())
    {
        var binaryFormatter = new BinaryFormatter();
        var decompressed = Decompress(arrBytes);

        memoryStream.Write(decompressed, 0, decompressed.Length);
        memoryStream.Seek(0, SeekOrigin.Begin);

        return binaryFormatter.Deserialize(memoryStream);
    }
}

The DeSerialize method works the other way around. A byte array is passed into the method, and then decompressed. Then the byte array is de-serialized into a MemoryStream with anotherBinaryFormatter. The resulting Object is then returned back to the called.

Usage of this object is very straight forward. Lets assume we have a class we want to serialize to a byte array called Payment.

[Serializable]
public class Payment
{
    public string CardNumber;
    public decimal Amount;
}

This example class contains a card number and a payment amount that we want to send to a payment system. The object must contain the [Serializable] attribute otherwise you will get a SerializationException thrown. To serialize this into a byte array we do the following.

var payment = new Payment { Amount = 5.0m, CardNumber = "1234123412341234" };
byte[] serialized = payment.Serialize();

The resulting serialized byte array contains our class and is compressed with GZip. To deserialize the byte array back into a Payment object we do the following.

Payment deserialized = (Payment)payment.DeSerialize();

This is a simple, but useful example. You could also easily extend this ObjectSerializer to encrypt the data as it is serialized.

Is This a Good Idea

If you are working on a system where you need to send objects to another system via a message broker like RabbitMQ for example, then you need to be careful if you are doing binary serialization of POCO’s.

If your destination system is also written in .NET then you will be able to de-serialize your objects on the other side, but you will be coupled to a .NET implementation. If you had another consumer that was written in Java for example then you would not be able to decode your messages.

You also need to be carefull about how you version your objects. If they are shared objects in your system that are used by other code, then it would be easy for another developer to potentially change the object without necessarily knowing if there would be an impact in another system. You should really have a separate DTO object to decouple from any other internal objects.

A better solution to serializing POCO’s using a BinaryFormatter, is to use XML or JSON as your message format. You can still use POCO’s internally but instead of serializing the object directly into a byte array, you can serialize to XML or JSON. This will mean that you are not tied to an implementation technology in your other systems. If you use a common format like XML or JSON, then you can easily interoperate with any other system whether it is developed in Java, Erlang, or any other language.

The completed code for the ObjectSerializer is contained below.

using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;

namespace RabbitMQ.Examples
{
    public static class ObjectSerialize
    {
        public static byte[] Serialize(this Object obj)
        {
            if (obj == null)
            {
                return null;
            }

            using (var memoryStream = new MemoryStream())
            {
                var binaryFormatter = new BinaryFormatter();

                binaryFormatter.Serialize(memoryStream, obj);

                var compressed = Compress(memoryStream.ToArray());
                return compressed;
            }
        }

        public static Object DeSerialize(this byte[] arrBytes)
        {
            using (var memoryStream = new MemoryStream())
            {
                var binaryFormatter = new BinaryFormatter();               
                var decompressed = Decompress(arrBytes);

                memoryStream.Write(decompressed, 0, decompressed.Length);
                memoryStream.Seek(0, SeekOrigin.Begin);

                return binaryFormatter.Deserialize(memoryStream);                
            }            
        }

        private static byte[] Compress(byte[] input)
        {
            byte[] compressesData;

            using (var outputStream = new MemoryStream())
            {
                using (var zip = new GZipStream(outputStream, CompressionMode.Compress))
                {
                    zip.Write(input, 0, input.Length);
                }

                compressesData = outputStream.ToArray();
            }

            return compressesData;
        }

        private static byte[] Decompress(byte[] input)
        {
            byte[] decompressedData;

            using (var outputStream = new MemoryStream())
            {
                using (var inputStream = new MemoryStream(input))
                {
                    using (var zip = new GZipStream(inputStream, CompressionMode.Decompress))
                    {
                        zip.CopyTo(outputStream);
                    }
                }

                decompressedData = outputStream.ToArray();
            }

            return decompressedData;
        }
    }
}
Advertisements

4 comments

  1. Well written! Thanks for sharing. You might want to check the compression ratio, in my experience compressing small objects tends to make them bigger 🙂

    OrigoDB uses BinaryFormatter serialization for messages, snapshots, writing to the transaction journal and cloning query results. You can plugin different IFormatter implementations for the different usages. Protobuf-net is awesome, it’s fast and the size is very small.

  2. Stephen – if you are using Rabbit and have .Net at both ends then I would suggest looking at EasyNetQ as it is a thing of beauty and deals with a lot of the gnarly Rabbit stuff in a really logical manner. Plus you can simply do bus.Publish(blah) and it will work out the serialisation and deserialisation for you (it uses Json by default, can be swapped out if you prefer another).

    1. Hi Morgan. EasyNetQ looks really nice. I hadn’t come across that before. Thanks.

      Any other interesting frameworks that have caught your eye? I quite like the look of Nancy.NET

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