Search code examples
javamodelmapper

Using ModelMapper to map field of abstract type


I have the following class hierarhy:

public abstract class Base {
    protected Boolean baseBoolean;
}
public class A extends Base {
    private BigDecimal amount;
}

And trying to map DTO to entity

public class DTO {
    private Base details;
}
public class Entity {
    private Base details;
}

And map as follows:

    public static void main(String[] args) {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration().setDeepCopyEnabled(true);

        A a = new A();
        a.setAmount(BigDecimal.ONE);
        a.setBaseBoolean(true);
        DTO request = DTO.builder().base(a).build();

        Entity entity = modelMapper.map(request, Entity.class);
        System.out.println(entity);
    }

I recieve DTO with A or B in details field, this is checked wiht debugger. But modelmapper throws

Failed to instantiate instance of destination org.package.Base. Ensure that org.package.Base has a non-private no-argument constructor.

I tried to use explicit Provider (which was not used for this mappping):

modelMapper.typeMap(A.class, Base.class).setProvider(new Provider<Base>() {
            @Override
            public Base get(ProvisionRequest<Base> r) {
                return new A();
            }
        });

I also tried to implement custom converter like this (which did not execute either):

modelMapper.typeMap(A.class, Base.class).setConverter(new Converter<A, Base>() {
            @Override
            public Base convert(MappingContext<A, Base> mappingContext) {
               return modelMapper.map(mappingContext.getSource(), A.class);
            }
         });

It seems that modelmapper don't use this typemaps for fields, only for root of hierarhy. How can I map class hierarhies in case like this?


Solution

  • As you have found, the problem occurs when deep copy is enabled: modelMapper.getConfiguration().setDeepCopyEnabled(true).

    A solution is to define a Converter<Base, Base> like follows:

    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration().setDeepCopyEnabled(true); // this triggers the problem
    
    // the converter resolves it
    Converter<Base, Base> baseBaseConverter = context ->
            modelMapper.map(context.getSource(), context.getSource().getClass());
    
    modelMapper.createTypeMap(Base.class, Base.class).setConverter(baseBaseConverter);
    

    Live demo

    Here is a more detailed post on the topic.