-
Notifications
You must be signed in to change notification settings - Fork 174
Dynamic type handling
NOTE: This article does NOT describe about dynamic primitive (a.k.a. System.Dynamic.DynamicObject
)
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.
// 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 );
...
}
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 simpleMessagePackObject
serialization. - You eventually get boxed
MessagePackObject
when you unpack the object with serializer forSystem.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
.
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 );
}
}
- "MessagePackObject type":http://cli.msgpack.org/doc/html/T_MsgPack_MessagePackObject.htm