Skip to content

Dynamic type handling

Bart Kevelham edited this page Aug 23, 2016 · 5 revisions

NOTE: This article does NOT describe about dynamic primitive (a.k.a. System.Dynamic.DynamicObject)

Motivation

Some application level protocols specify dynamic typed fields, and you might have to handle dynamic object which generally be represented as dictionary. For example, the API returns type code and type specific values when it successes, or returns error code when it fails. Using MessagePackObject, you can handle such objects.

Samples

// Gets an object which stores dynamic key/value pairs as dictionary object.
Dictionary<MessagePackObject, MessagePackObject> mpoDict = MessagePackSerializer.Create<Dictionary<MessagePackObject, MessagePackObject>>().UnpackFrom(...);

Debug.Assert( dict.Keys.All( v => v.IsTypeOf<String>() );
// Converts dict keys. Note that this line assumes there are no key duplications.
Dictionary<String, MessagePackObject> dict= mpoDict.ToDictionary( kv => (String)kv.Key, kv => kv.Value );

MessaePackObject theValue;
if ( dict.TryGetValue( "error", out theValue ) )
{
     // The data must be error.
     Debug.Assert( theValue.IsTypeOf<String>() );
    // Convert utf-8 binaries to a String object.
     var error = (String) theValue;
     ...
}
else if ( dict.TryGetValue( "results", out theValue ) )
{
    Debug.Assert( theData.IsList );
    // First get values as list.
    IList<MessagePackObject> values = (IList<MessagePackObject>)theValue;
    Debug.Assert( !values.Any() || values.All( v => v.IsTypeOf<Double>()) );
    // Convert values.
    var results = values.Select( v => (double )v );
    ...
}

System.Object vs MsgPack.MessagePackObject

Although you can use System.Object for dynamic type field packing, it has some problems.

  • Packing System.Object causes runtime serializer searching, it requires more time (about 1000 times) than simple MessagePackObject serialization.
  • You eventually get boxed MessagePackObject when you unpack the object with serializer for System.Object because the serializer cannot infer actual type to be deserialized.

Because every serializable object can be represented as array or map ultimately, it is recommended to use MessagePackObject rather than System.Object.

Deserializing Polymorphic Object

You can serialize/deserialize polymorphic object with custom hand-made serializer, IPackable and IUnpackable, and single type code field as following:

abstract class DynamicMessage : IPackable, IUnpackable
{
    // Flag to identify known derived type
    private int _typeCode;

    // Use internal constructor to ensure all derived types are known.
    internal DynamicMessage(int typeCode) {
        this._typeCode = typeCode;
    }

    public void PackToMessage( Packer packer )
    {
        packer.Pack( this._typeCode );
        this.PackToMessageCore( packer );
    }

    // Implements packing for derived classes' fields.
    protected abstract void PackToMessageCore( Packer packer );

    public void UnpackFromMessage( Unpacker unpacker )
    {
        this._typeCode = unpacker.LastReadData;
        DynamicMessageSerializer.ReadNext( unpacker );

        this.UnpackFromMessageCore( unpacker );
    }

    // Implements unpacking for derived classes' fields.
    protected abstract void UnpackFromMessageCore( Unpacker unpacker );
}

// Known derived type 1
class MessageType1 : DynamicMessage 
{
    public const int TypeCode = 1;
    public int Value1 { get; set; }
    public string Value2 { get; set; }

    public MessageType1() : base( TypeCode ) {}

    protected override void PackToMessageCore( Packer packer )
    {
        packer.PackArrayHeader( 2 );
        packer.Pack( this.Value1 );
        packer.Pack( this.Value2 );
    }

    protected override void UnpackFromMessageCore( Unpacker unpacker )
    {
        Debug.Assert( unpacker.IsArrayHeader );
        Debug.Assert( unpacker.LastReadData == 2 );
        DynamicMessageSerializer.ReadNext( unpacker );
        this.Value1 = ( int )unpacker.LastReadData;
        DynamicMessageSerializer.ReadNext( unpacker );
        this.Value2 = ( string )unpacker.LastReadData;
    }
}

// Known derived type 2
class MessageType2 : DynamicMessage 
{
    public const int TypeCode = 2;
    public bool Value1 { get; set; }

    public MessageType2() : base( TypeCode ) {}

    protected override void PackToMessageCore( Packer packer )
    {
        packer.PackArrayHeader( 1 );
        packer.Pack( this.Value1 );
    }

    protected override void UnpackFromMessageCore( Unpacker unpacker )
    {
        Debug.Assert( unpacker.IsArrayHeader );
        Debug.Assert( unpacker.LastReadData == 1 );
        DynamicMessageSerializer.ReadNext( unpacker );
        this.Value1 = ( bool )unpacker.LastReadData;
    }
}

// Custom serialier
class DynamicMessageSerializer : MessagePackSerializer<DynamicMessage>
{
    public DynamicMessageSerializer() {}

    protected override void PackToCore( Packer packer, DynamicMessage objectTree )
    {
        // Invokes IPackable.PackToMessage to handle actual unpacking.
        objectTree.PackToMessage( packer );
    }

    protected override DynamicMessage UnpackFromCore( Unpacker unpacker )
    {
        // To invoke instance IUnpackable methods, custom serializer must handle instantiation with known field.
        DynamicMessage result;
        switch( ( int )unpacker.LastReadData )
        {
            case MessageType1.TypeCode:
            {
                result = new MessageType1();
                break;
            }
            case MessageType2.TypeCode:
            {
                result = new MessageType2();
                break;
            }
            default:
            {
                throw new SerializationException( String.Format( "{0} is unknown type code.", ( int )unpacker.LastReadData ) );
            }
        }

        ReadNext( unpacker );

        // Invokes IUnpackable.UnpackFromMessage to handle actual unpacking.
        result.UnpackFromMessage( unpacker );

        return result;
    }

    // Helper method
    internal static void ReadNext( Unpacker unpacker )
    {
        if ( !unpacker.Read() )
        {
            throw new SerializationException( "Message unexpectedly ends." );
        }
    }
}

// Application code
class App
{
    void Foo(...)
    {
        ...
        // Creates application specific common context.
        var context = new SerializationContext();
        // Registers custom serializer.
        context.Serializers.Register<DynamicMessage>( new DynamicMessageSerializer() );
        ...

        var serializer = MessagePackSerializer.Create<DynamicMessage>( context );
        ...
        var serializingMessage = new MessageType1();
        serializer.Pack( stream, serializingMessage );
        ...
        var deserializedMessage = serializer.Unpack( stream );
    }
}

See Also