-
Notifications
You must be signed in to change notification settings - Fork 174
Custom serialization
msgpack-cli's built-in serialization mechanism can support most cases, but there are edge cases which it does not support. For example:
- The type defines its own binary representation.
- The requirement for serialization/deserialization speed is critical (note that default implementation generates straightforward code -- it has good performance).
- You do not refer
*.Serialization
assembly in Silverlight/WindowsPhone project to reduce application size. In this case, you must use Manually packing/unpacking because the built-in serialization mechanism is contained in those assembly.
This article discusses the first and second items above. See Manual packings for third item.
There are two options to implement custom serialization -- IPackable/IUnpackable
and Custom Serializer.
The easiest way to implement custom serialization is implementing IPackable
and IUnpackable
on your class. In this case, you will interact with packers/unpackers via MessagePackObject
.
Note that the serialization mechanism prefers to use registered (custom) serializers even if the type implements IPackable
and IUnpackable
because the runtime generate dedicated serializer for the type, which delegates actual serialization process to IPackable
/IUnpackable
methods.
The type must have default public constructor even if it implements IPackable
and IUnpackable
.
If you want to serialize existing types, want to share the serialization logic in a number of MessagePackSerialier
, or the type does not have default public constructor, you can implement your own custom MessagePackSerializer
.
In this case, you must register its instance to the used serialization context.
To use custom serializer, you must do following:
- Create dedicated
SerializationContext
instance. - Implement custom serializer. The serializer must derived from
MessagePackSerializer<T>
, and implements its abstract methods.-
PackToCore
method. It is similar toIPackable.PackTo
method. Implement packing logic from the instance to the stream viaPacker
object. -
UnpackFromCore
method. It is similar toIUnpackable.UnpackFrom
method. Implement unpacking logic which pulls data from the stream viaUnpacker
object and deserialize to newly created instance. - Optional
UnpackToCore
method. IfT
is collection and it has public default constructor, this method implements item feeding style deserialization. Specifically, if the hosted type hasT
type member which is read only and initialized in its constructor, the serializer will useUnpackTo
to deserialize elements for the read only collection member.
-
- Create custom serializer using dedicated serialization context.
- Register serializer instance to
SerialzationRepository
viaSerializationContext.Serializers
property. As of 0.6.0, you can register on demand via the event handler forSerializationContext.ResolveSerializer
event.
If you use MessagePackSerializer.Create
to get a built-in serializer, you must pass a dedicated serialization context so that the generated serializers use registered custom serializers. Also, you must pass dedicated serialization context to use custom serializers in MessagePack RPC APIs.
Here is a sample custom DateTimeOffset
serializer implementation:
[C#]
public class CustomDateTimeOffsetSerializer : MessagePackSerializer<DateTimeOffset>
{
public CustomDateTimeOffsetSerializer()
: this( SerializationContext.Default ) { }
public CustomDateTimeOffsetSerializer( SerializationContext context )
{
// If the target objects has complex (non-primitive) objects,
// you can get serializers which can handle complex type fields.
// And then, you can cache them to instance fields of this custom serializer.
}
protected override void PackToCore( Packer packer, DateTimeOffset value )
{
// First, pack array length (or map length).
// It should be a number of members of the object to be serialized.
packer.PackArrayHeader( 2 );
// If you choice array encoding, just pack fields in order.
packer.Pack( value.Ticks );
packer.Pack( ( short )value.Offset.TotalMinutes );
}
protected override DateTimeOffset UnpackFromCore( Unpacker unpacker )
{
// unpacker should be located in the underlying stream.
// It should not be the head of the stream.
Contract.Assume( unpacker.IsArrayHeader || unpacker.IsMapHeader );
// TIPS: You might not be able to determine that the incoming stream contains
// array encoded msgpack stream or map encoded msgpack stream.
// So, you should implement both unpacking logic.
if ( unpacker.IsArrayHeader )
{
// Note exception handling is omitted...
// Fetch next field.
if ( !unpacker.Read() )
{
throw new SerializationException();
}
// Data is MessagePackObject, so you can cast it to primitive value.
var ticks = ( long )unpacker.Data.Value;
// As of 0.3, you can use ReadXxx(out T) method.
short offset;
if ( !unpacker.ReadInt16( out offset ) )
{
throw new SerializationException();
}
return new DateTimeOffset( ticks, TimeSpan.FromMinutes( offset ) );
}
else // Map encoded
{
// You should not assume that keys are ordered as you expect.
string key;
long ticks = 0;
short offset = 0;
bool ticksFound = false;
bool offsetFound = false;
while ( unpacker.ReadString( out key ) )
{
// Assume key is property name...
switch ( key )
{
case "Ticks":
{
if ( !unpacker.ReadInt64( out ticks ) )
{
throw new SerializationException();
}
ticksFound = true;
break;
}
case "Offset":
{
if ( !unpacker.ReadInt16( out offset ) )
{
throw new SerializationException();
}
offsetFound = true;
break;
}
default:
{
// Unknown key.
throw new SerializationException();
}
}
}
if ( !ticksFound || !offsetFound )
{
// Incompleted stream
throw new SerializationException();
}
return new DateTimeOffset( ticks, TimeSpan.FromMinutes( offset ) );
}
}
}