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?

+3


source to share


1 answer


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);

      

+2


source







All Articles