msgpack-cli icon indicating copy to clipboard operation
msgpack-cli copied to clipboard

Problem With Registering Polymorphic Objects

Open AveryBoyd opened this issue 5 years ago • 0 comments

Registering polymorphic objects directly does not work correctly when trying to serialize those objects from other objects.

Here is an example:

public class Wrapper
{
    public Letters field { get; set; }
}

public interface Letters
{

}

public class A : Letters
{
    public string ItemA { get; set; }
}

public class B : Letters
{
    public string ItemB { get; set; }
}

public abstract class SubType
{
}

public class SubTypeA : SubType
{
    public string ItemD { get; set; }
}

public class SubTypeB : SubType
{
    public string ItemE { get; set; }
}

As shown, the polymorphic 'Letters' is within the class 'Wrapper'. Now trying to register these to the SerializationContext...

        SerializationContext context = new SerializationContext();

        context.GetSerializer<SubType>(
            PolymorphismSchema.ForPolymorphicObject(
                typeof(SubType),
                new Dictionary<string, Type>
                {
                    {"A", typeof(SubTypeA)},
                    {"B", typeof(SubTypeB)}
                }));

        context.GetSerializer<IType>(
            PolymorphismSchema.ForPolymorphicObject(
                typeof(IType),
                new Dictionary<string, Type>
                {
                    {"A", typeof(TypeA)},
                    {"B", typeof(TypeB)}
                }));

        MessagePackSerializer<Wrapper> serializerWrapper = context.GetSerializer<Wrapper>();
        // The above line breaks due to 'System.NotSupportedException: 'This operation is not supported because 'IType' cannot be instantiated.'

`

This shouldn't happen, because the context should internally register the serializers it gets.

From what I've dug up, the problem is that the serializer that context.GetSerializer creates is null for polymorphic types.

In SerializationContext.cs line 692: serializer = this.OnResolveSerializer<T>( schema ) ?? MessagePackSerializer.CreateInternal<T>( this, schema ); MessagePackSerialier.CreateInternal is called. And then in MessagePackSerializer.Factories.cs lines 247-251:

if ( concreteType == null )
{
    // return null for polymoirphic provider.
    return null;
}

The returned serializer is null, and so the provider is given a null default serializer at SerializationContext.cs line 724: provider = new PolymorphicSerializerProvider<T>( serializer ); And then when SerializationContext.cs line 741 is reached:

this._serializers.Register(
    typeof( T ),
    provider,
    nullableType,
    nullableSerializerProvider,
    SerializerRegistrationOptions.WithNullable
)

A faulty serializer is registered.

But, context.GetSerializer returns the correct serializer because SerializationContext.cs line 760 return this._serializers.Get<T>( this, providerParameter ); will use the schema to create the correct serializer.

A work around for this issue is:

   context.Serializers.RegisterOverride(
        context.GetSerializer<IType>(
            PolymorphismSchema.ForPolymorphicObject(
                typeof(IType),
                new Dictionary<string, Type>
                {
                    {"A", typeof(TypeA)},
                    {"B", typeof(TypeB)}
                })));

Where the correctly returned serializer is then re-registered with the context.

But, I propose solving this issue by changing the MessagePackSerializer.Factories.cs lines 247-251 to this:

if ( concreteType == null )
{
    if ( schema == null )
    {
	    return null; // maybe do something else here?
    }
    if ( schema.UseTypeEmbedding )
    {
	    return new TypeEmbedingPolymorphicMessagePackSerializer<T>( context, schema );
    }
    else
    {
	    return new KnownTypePolymorphicMessagePackSerializer<T>( context, schema );
    }
}

This is what PolymorphicSerializerProvider.cs does anyway at the end of context.GetSerializer.

AveryBoyd avatar Sep 04 '18 17:09 AveryBoyd