Using MsgPack with Servicestack: How can I use KnownType?
I am trying to support the MessagePack protocol in my current Servicestack version. I need it to support (de) serialize an ISegment list defined like this:
[KnownType(typeof(ArcSegment)), KnownType(typeof(LineSegment))]
public class PathRequest
{
public List<ISegment> Segments {get;set;}
}
public interface ISegment
{
Point StartPoint {get;set;}
Point EndPoint {get;set;}
}
public class ArcSegment: ISegment {...}
public class LineSegment: ISegment {...}
Out of the box I was told that
The Asi.Geometry.ISegment type has neither a public default constructor (without parameters) nor a public constructor with an Int32 parameter.
This is very true. Apparently it doesn't use the KnownType attribute. After reading online, I found that I could make my own serializer. So I tried this:
class ArcLineSerializer: MessagePackSerializer<ISegment>
{
private readonly MessagePackSerializer<ArcSegment> _arcSerializer = MessagePackSerializer.Create<ArcSegment>();
private readonly MessagePackSerializer<LineSegment> _lineSerializer = MessagePackSerializer.Create<LineSegment>();
protected override void PackToCore(Packer packer, ISegment objectTree)
{
if(objectTree is ArcSegment)
_arcSerializer.PackTo(packer, (ArcSegment)objectTree);
else if (objectTree is LineSegment)
_lineSerializer.PackTo(packer, (LineSegment)objectTree);
else
throw new NotSupportedException();
}
protected override ISegment UnpackFromCore(Unpacker unpacker)
{
var data = unpacker.Data;
if (data != null)
{
if (data.Value.IsTypeOf<ArcSegment>().GetValueOrDefault())
return _arcSerializer.UnpackFrom(unpacker);
if (data.Value.IsTypeOf<LineSegment>().GetValueOrDefault())
return _lineSerializer.UnpackFrom(unpacker);
throw new NotSupportedException();
}
return null;
}
}
Alas, this is giving me the same error trying to build the _arcSerializer. How it's done?
source to share
You cannot just get the type of the wrapped class from the message, but you can pass a specific type identifier along with the serialized data.
Here's how to do it in a general way to serialize an interface.
class InterfaceSerializer<T> : MessagePackSerializer<T>
{
private Dictionary<string, IMessagePackSerializer> _serializers;
public InterfaceSerializer()
: this(SerializationContext.Default)
{
}
public InterfaceSerializer(SerializationContext context)
{
_serializers = new Dictionary<string, IMessagePackSerializer>();
// Get all types that implement T interface
var implementingTypes = System.Reflection.Assembly
.GetExecutingAssembly()
.DefinedTypes
.Where(t => t.ImplementedInterfaces.Contains(typeof(T)));
// Create serializer for each type and store it in dictionary
foreach (var type in implementingTypes)
{
var key = type.Name;
var value = MessagePackSerializer.Create(type, context);
_serializers.Add(key, value);
}
}
protected override void PackToCore(Packer packer, T objectTree)
{
IMessagePackSerializer serializer;
string typeName = objectTree.GetType().Name;
// Find matching serializer
if (!_serializers.TryGetValue(typeName, out serializer))
{
throw SerializationExceptions.NewTypeCannotSerialize(typeof(T));
}
packer.PackArrayHeader(2); // Two-element array:
packer.PackString(typeName); // 0: Type name
serializer.PackTo(packer, objectTree); // 1: Packed object
}
protected override T UnpackFromCore(Unpacker unpacker)
{
IMessagePackSerializer serializer;
string typeName;
// Read type name and packed object
if (!(unpacker.ReadString(out typeName) && unpacker.Read()))
{
throw SerializationExceptions.NewUnexpectedEndOfStream();
}
// Find matching serializer
if (!_serializers.TryGetValue(typeName, out serializer))
{
throw SerializationExceptions.NewTypeCannotDeserialize(typeof(T));
}
// Unpack and return
return (T)serializer.UnpackFrom(unpacker);
}
}
You need to register custom serializers for the required interfaces. For example, when using the default serialization context:
SerializationContext.Default.Serializers
.Register<ISegment>(new InterfaceSerializer<ISegment>());
Since the registrar has been registered, objects containing ISegment can be packed and unpacked as usual.
var packer = MessagePackSerializer.Create<PathRequest>();
packer.Pack(stream, somePathRequest);
stream.Position = 0;
var unpackedPathRequest = packer.Unpack(stream);
source to share