Skip to content

10.0.7: nullable abstract property maps through abstract base type and throws Cannot instantiate type #928

@luczito

Description

@luczito

I upgraded from Mapster 9.0.0-pre01 to 10.0.7.

After the upgrade, mapping an object that contains a nullable abstract/base-type property fails when the source property value is null.

The mapping is configured with Include<TDerivedSource, TDerivedDestination>(), so I would expect Mapster to use the included derived mapping when the source value is non-null, and simply map null to null when the source value is null.

Repro

public abstract class DtoBase
{
}

public class DtoDerived : DtoBase
{
    public string Id { get; set; } = null!;
}

public class DtoFoo
{
    public DtoBase? Field { get; set; }
}

public abstract class DomainBase
{
}

public class DomainDerived : DomainBase
{
    public string Id { get; set; } = null!;
}

public class DomainFoo
{
    public DomainBase? Field { get; set; }
}

Global Configuration:

public static TypeAdapterConfig ConfigureMapster(MappingAssemblyProvider mappingAssemblyProvider)
{
    var config = TypeAdapterConfig.GlobalSettings;

    config.Default.PreserveReference(true);
    config.Default.EnumMappingStrategy(EnumMappingStrategy.ByName);
    config.Default.EnableNonPublicMembers(true);
    config.Default.MapToConstructor(true);
    config.Default.ShallowCopyForSameType(false);

    config.AllowImplicitSourceInheritance = true;
    config.RequireDestinationMemberSource = true;

    foreach (var assembly in mappingAssemblyProvider.GetMapperAssemblies())
    {
        config.Scan(assembly);
    }

    config.Compile();

    return config;
}

public class MappingConfig : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        config.ForType<DtoBase, DomainBase>()
            .TwoWays()
            .Include<DtoDerived, DomainDerived>();
    }
}

Test:

[TestFixture]
public class RecordMappingTests
{
    [SetUp]
    public void SetUp()
    {
        MappingConfiguration.ConfigureMapster(new MappingAssemblyProvider());
    }

    [Test]
    public void DtoFooWithFieldNull_Maps_To_DomainFoo()
    {
        var dto = new DtoFoo
        {
            Field = null
        };

        var domain = dto.Adapt<DomainFoo>();

        Assert.That(domain.Field, Is.Null);
    }

    [Test]
    public void DtoFooWithField_Maps_To_DomainFoo()
    {
        var dto = new DtoFoo
        {
            Field = new DtoDerived
            {
                Id = "id"
            }
        };

        var domain = dto.Adapt<DomainFoo>();

        Assert.That(domain.Field, Is.Not.Null);
        Assert.That(domain.Field, Is.TypeOf<DomainDerived>());
    }
}

The DtoFooWithFieldNull_Maps_To_DomainFoo test fails with:

System.InvalidOperationException : Cannot instantiate type: DomainBase

at lambda_method30(Closure, DtoBase)
at lambda_method32(Closure, Object)
at Mapster.TypeAdapter.Adapt[TDestination](Object source, TypeAdapterConfig config)
at Mapster.TypeAdapter.Adapt[TDestination](Object source)
at RecordMappingTests.RecordMappingTests.DtoFooWithFieldNull_Maps_To_DomainFoo()`

This only happens then the adapt call is made, and the compile() call succeeds. It looks like Mapster is attempting to map the nested Field property through the declared base mapping, even when the source value is null.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions