1

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?

Ax_yv
  • 48
  • 7
  • Did you try adding a non-private no-argument constructor in Base class as said in the exception? – Brijesh Bhatt Dec 13 '19 at 10:36
  • But Base is abstract, it actually fails when mm trying to instantiate Base using reflection. I guess there should be a way to explicitly tell modelmapper to istantiate not Base but the same implementation class that is recieved in DTO, but i can't find how – Ax_yv Dec 13 '19 at 10:46
  • Please provide a [reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) as I cannot [reproduce](https://jdoodle.com/a/1Lll) the issue with the code posted so far. – MartinBG Dec 13 '19 at 13:21
  • @MartinBG thanks, i added example of main method that fails with exception provided above. Please note that some lombok annotations is omitted for brevity. – Ax_yv Dec 13 '19 at 14:15
  • Also, I discovered that this issue could be solved by disabling deepcopy (which isn't always acceptable unfortunately) – Ax_yv Dec 13 '19 at 14:17

1 Answers1

1

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.

MartinBG
  • 1,500
  • 13
  • 22
  • 1
    Thanks, that helped. However, i discovered one more very tricky thing about modelmapper: if i use `modelMapper.typeMap(DTO.class, Entity.class)` before `modelMapper.createTypeMap(Base.class, Base.class)` it still throws the exception. It seems that if class DTO has field of type Base typeMap for Base should be defined BEFORE typeMap for DTO. – Ax_yv Dec 16 '19 at 08:22