Skip to content

Polymorphic mapping of nested property to null property. #794

Closed

Description

@vchc

Hi all!

Mapster 7.4.0

I encountered some strange behavior with mapping of polymorphic nested properties.
The task is to map a nested polymorhic property of source (DTO) to the nested polymorphic property of destination.

Expected result of mapping for actual types is.
BaseDTO -> Base
DerivedDTO-> Derived

When destination nested property is null it is always mapped as base class even when the source nested property is derived class.
DerivedDTO-> Derived - not works when destination property is null

May be it is expected, but it looks like a bug for me. Or may be there is a mistake in my configuration?

public class Base
{
    public string SomeBaseProperty { get; set; }
}

public class BaseDTO
{
    public string SomeBasePropertyDTO { get; set; }
}

public class Derived : Base
{
    public string SomeDerivedProperty { get; set; }
}

public class DerivedDTO : BaseDTO
{
    public string SomeDerivedPropertyDTO { get; set; }
}

public class Container
{
    public Base Nested { get; set; }
}

public class ContainerDTO
{
    public BaseDTO NestedDTO { get; set; }
}

            config
                .NewConfig<ContainerDTO, Container>()
                .Map(dest => dest.Nested, src => src.NestedDTO )
                .IgnoreNonMapped(true)
                .IgnoreNullValues(true);

            config
                .NewConfig<BaseDTO, Base>()
                .Map(dest => dest.SomeBaseProperty , src => src.SomeBasePropertyDTO)
                .Include<DerivedDTO, Base>()
                .IgnoreNonMapped(true)
                .IgnoreNullValues(true);

            config
                .NewConfig<DerivedDTO, Derived>()
                .Map(dest => dest.SomeDerivedProperty , src => src.SomeDerivedPropertyDTO)
                .IgnoreNonMapped(true)
                .IgnoreNullValues(true);

            config
                .NewConfig<DerivedDTO, Base>()
                .MapWith(src => src.Adapt<Derived>());

//Case 1 (works)
var container = new();
var containerDTO = new();

//Nested is populated with Base
container.Nested = new();
containerDTO.NestedDTO = new DerivedDTO();

//Polymorphism works for Nested property
mapper.Map<ContainerDTO, Container>(containerDTO, container);

//Case 2 (does not work)
var container = new();
var containerDTO = new();

//Nested is null
container.Nested = null;
containerDTO.NestedDTO = new DerivedDTO();

//Polymorphism does not work for Nested property. It is always Base.
mapper.Map<ContainerDTO, Container>(containerDTO, container);

Activity

  1. DocSvartz commented on Apr 29, 2025

    @DocSvartz
    Contributor

    Yes, this does happen.
    But it most likely has nothing to do with Polymorphic mapping.
    Most likely, this is the work of the protection mechanism against mapping to Null.

    In your case (MapToTarget), the state of the internal fields of the Destination instance should be replaced with the transformed values ​​of the Source.

    But since this is null, to avoid null Reference exception -> an instance of the declared variable type is created (in your case it is Base).

    This can be fixed, but we just need to understand what behavior is expected in this case?

  2. vchc commented on May 1, 2025

    @vchc
    Author

    This can be fixed, but we just need to understand what behavior is expected in this case?

    I expect some consistency here. For collections it works just fine. You can have a null or empty collection property on the destination and polymorphic mapping will work with my configuration.

    I still think it is a bug.

    Moreover I provide construction rule with

    config
                    .NewConfig<DerivedDTO, Base>()
                    .MapWith(src => src.Adapt<Derived>());
    

    and expect that the null property will be handled with my rule.

    So my expectations are:

    1. If the destination is not null - use the actual types of objects.
    2. If the destination is null - use declaration type to construct an object and then map to it.
    3. If the destination is null and construction rule is present - use it to construct an object on destination property directly.
  3. DocSvartz commented on May 2, 2025

    @DocSvartz
    Contributor

    @vchc
    The consistency is actually there, it's just not what you expect.

    1. There are three mapping mechanisms Map, MapToTarget and Projection and they have different limitations and features.
    2. Map is creating an object of the destination type based on the source
      MapToTarget is updating an existing instance of the destination type with the state of the source instance.

    You can have a null or empty collection property on the destination and polymorphic mapping will work with my configuration.

    MapToTarget does not work for collections (I don't know if this was originally intended or didn't have time to do it)

    I found where it happened. Tests are currently running. If there are no other errors, it will work :)

  4. vchc commented on May 3, 2025

    @vchc
    Author

    Thanks for the PR @DocSvartz !

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