Skip to content

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

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.

Activity

  1. DocSvartz commented on Apr 24, 2026

    @DocSvartz
    Contributor

    Hi @luczito Are you sure the above types are correct?

    This doesn't work for me in any version of Mapster.

  2. luczito commented on Apr 24, 2026

    @luczito
    Author

    @DocSvartz They should be but i might have missed some fields / inheritance in my repro that is present in the codebase. Nonetheless i would expect the behavior to check for null before attempting to map the nested properties.

  3. DocSvartz commented on Apr 24, 2026

    @DocSvartz
    Contributor

    @luczito Try running the test as it written in the examples and you'll see that its fail, but for completely different reasons. 🤔

    If you refine your example to a version that can work, you'll get the result you need.

    [TestMethod]
    public void Async()
    {
        var config = new TypeAdapterConfig();
    
        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;
    
    
    
        config.ForType<DtoBase, DomainBase>()
        .Include<DtoDerived, DomainDerived>()
        .TwoWays();
        config.Compile();
    
    
        var dto = new DtoFoo
        {
            Field = null
        };
    
        var domain = dto.Adapt<DomainFoo>(config); // no error  domain.Field == null
    
    
    
    
    public  class DtoBase
    {
    }
    
    public class DtoDerived : DtoBase
    {
        public string Id { get; set; } = null!;
    }
    
    public class DtoFoo
    {
        public DtoBase? Field { get; set; }
    }
    
    
    public  class DomainBase
    {
    }
    
    public class DomainDerived : DomainBase
    {
        public string Id { get; set; } = null!;
    }
    
    public class DomainFoo
    {
        public DomainBase? Field { get; set; }
    }
    
    }
    
  4. DocSvartz commented on Apr 24, 2026

    @DocSvartz
    Contributor

    .TwoWays(); It causes some kind of glitch in the abstract type-based hierarchy. But this has been the case since version 7.4.0.

    I found your error in version 10.0.7, need remove .TwoWays() then it throw

  5. added 2 commits that reference this issue on Apr 28, 2026
    06535e6
    04abab2
  6. DocSvartz commented on Apr 28, 2026

    @DocSvartz
    Contributor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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