C#: MessagePack Serialization

Introduction

In this tutorial we will learn how to serialize an object to the MessagePack format, in C#. We will be using msgpack-cli package, which can be easily installed from the Visual Studio NuGet package manager, as can be seen in figure 1.

Installing the MsgPack.Cli package from the NuGet package manager.
Figure 1 – Installing the MsgPack.Cli package from the NuGet package manager.

This tutorial was tested with .NET Core v3.1 and with MsgPack.Cli v1.0.1.

The code

The first thing we will do is defining a class to hold the data we want to serialize to the MessagePack format. For illustration purposes, we will define a Person class with a couple of attributes of different data types.

namespace MessagePack
{
    using System.Collections.Generic;

    public class Person
    {
        public Person(string name, int age, bool isMarried, List<string> languages)
        {
            Name = name;
            Age = age;
            IsMarried = isMarried;
            Languages = languages;
        }

        public string Name { get; }

        public int Age { get; }

        public bool IsMarried { get; }

        public List<string> Languages { get; }

    }

}

Now that we have our class, we will move to the Main code, where we will perform the MessagePack serialization of an object of that class.

The first thing we will do is stating the namespace imports. We will need the MsgPack.Serialization namespace, from the package we have installed. This will make available the serializer we need.

using MsgPack.Serialization;

We will also need some System namespaces, which we will later correlate with the classes we will be using below.

using System;
using System.Collections.Generic;
using System.IO;

Moving on to the Main code, we will start by creating an object of class Person, with some arbitrary values. Note that one of the parameters is a list, which explains why we are using the System.Collections.Generic namespace.

var person = new Person(
        name: "John",
        age: 20,
        isMarried: false,
        languages: new List<string>
        {
            "portuguese",
            "english"
        }
);

After this, we will create an object of class SerializationContext, which allows us to specify some serialization options. In particular, we are going to set the serialization method to map, meaning the Key – Value information of our original object is preserved upon the serialization to the MessagePack binary format [1]. In our simple use case, I’m using map just to illustrate how to set this option.

Note that we need to explicitly set this option because the package defaults to the serialization method array [1]. Both options have advantages and drawbacks: array serialization / deserialization has better performance but maintaining compatibility when adding new fields is more complex, whereas map serialization makes it easier to maintain compatibility but has less performance [1].

var context = new SerializationContext { SerializationMethod = SerializationMethod.Map };

After this we will create a MessagePack serializer instance. To do so, we simply need to call the Get static method on the MessagePackSerializer class. We need to specify as type parameter the class of the object we want to serialize which, in our case, is Person.

As input of this method, we will pass our previously created SerializationContext. This parameter is optional and we only need to pass it if we need to change the defaults.

var serializer = MessagePackSerializer.Get<Person>(context);

Next, we are going to create a MemoryStream, which will receive the bytes of the serialization process. Note that the MemoryStream class belongs to the System.IO namespace. We will wrap the creation of the MemoryStream in a using block since it is a disposeable object (although a MemoryStream does not have any resources to dispose [2]).

using (var byteStream = new MemoryStream())
{
   // Serialization process
}

To perform the actual serialization of the object, we simply need to call the Pack method on our serializer object, passing as first input our memory stream and as second input the object we want to serialize.

serializer.Pack(byteStream, person);

Then we will call the ToArray method on our stream to obtain a byte array with the content of the stream. In other words, we will obtain a byte array with the MessagePack serialized object.

var byteArray = byteStream.ToArray();

To finalize, we will print our array as an hexadecimal string, so we can use it in an online testing tool we will check in the next section. For a detailed tutorial on how to convert a byte array to an hexadecimal string in C#, please check here. Note that both the BitConverter and the Console classes belong to the System namespace.

var hexString = BitConverter.ToString(byteArray);
var hexStringWithoutDashes = string.Join(' ', hexString.Split('-'));

Console.WriteLine(hexStringWithoutDashes);

The complete C# main code can be seen below.

namespace MessagePack
{
    using MsgPack.Serialization;
    using System;
    using System.Collections.Generic;
    using System.IO;

    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person(
                name: "John",
                age: 20,
                isMarried: false,
                languages: new List<string>
                {
                   "portuguese",
                   "english"
                }
            );

            var context = new SerializationContext { SerializationMethod = SerializationMethod.Map };

            var serializer = MessagePackSerializer.Get<Person>(context);

            using (var byteStream = new MemoryStream())
            {

                serializer.Pack(byteStream, person);

                var byteArray = byteStream.ToArray();
                var hexString = BitConverter.ToString(byteArray);
                var hexStringWithoutDashes = string.Join(' ', hexString.Split('-'));

                Console.WriteLine(hexStringWithoutDashes);
            }
        }
    }
}

Testing the code

To test the previous code, simply compile it and run it in a tool of your choice. In my case, I’m using Visual Studio 2019.

Upon running the code, you should get an output similar to figure 2.

Output of the program, showing the bytes from the MessagePack serialization.
Figure 2 – Output of the program, showing the bytes from the MessagePack serialization.

To test the result of the serialization, we can use this online tool. Simply copy the bytes printed to the console and paste them on the tool. Figure 3 illustrates the result. As can be seen, we have obtained, in JSON format, the same object we serialized in the code.

Result of decoding the MessagePack bytes in an online tool.
Figure 3 – Result of decoding the MessagePack bytes in an online tool.

References

[1] https://github.com/msgpack/msgpack-cli/wiki/Interoperability-considerations#serialization-method

[2] https://docs.microsoft.com/en-us/dotnet/api/system.io.memorystream?view=netcore-3.1

Leave a Reply

%d bloggers like this: