5

I have an abstract class:

public abstract class AbstractBeanDTO<T> extends AbstractBigDTO {
protected T value;

//getter setter 
}

The extending class has the generic type List

public class ItemsBeanDTO extends AbstractBeanDTO<List<String>> {
}

Im trying to map the model class to DTO which also has the same structure.

AbstractBeanDTO<?> dto =  (AbstractBeanDTO<?>) modelMapper.map(modelBean, ItemsBeanDTO.class);

My modelMapper is configured to match strict. It is able to convert the other generic types like Long, Integer, String but not List.

I get the below error:

org.modelmapper.MappingException: ModelMapper mapping errors:

1) Failed to instantiate instance of destination java.util.List. Ensure that java.util.List has a non-private no-argument constructor.
Caused by: java.lang.NoSuchMethodException: java.util.List.<init>()
    at java.lang.Class.getConstructor0(Unknown Source)
    at java.lang.Class.getDeclaredConstructor(Unknown Source)
    at org.modelmapper.internal.MappingEngineImpl.instantiate(MappingEngineImpl.java:333)
    at org.modelmapper.internal.MappingEngineImpl.createDestination(MappingEngineImpl.java:348)
    at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:141)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:115)
    at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
    at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
    at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
    at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
    at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
    at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:115)
    at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:72)
    at org.modelmapper.ModelMapper.mapInternal(ModelMapper.java:573)
    at org.modelmapper.ModelMapper.map(ModelMapper.java:406)

I tried to map it explicitely using TypeMap, but its the same.

modelMapper.createTypeMap(ItemsBean.class, ItemsBeanDTO.class).addMappings(mapper -> {
            mapper.map(src -> src.getValue(), (dest, value) -> dest.setValue((List<String>) value));
        });

The only thing that works is when you explicitely declare the variable as List. (But I dont want to do it)

I'm using 2.3.5 version of ModelMapper and Java8. Could anyone please help me how this would work ?


I tried using typeMap and typeToken as below,

Type listType = new TypeToken<ItemsBeanDTO>() {
        }.getType();

modelMapper.createTypeMap(ItemsBean.class, ItemsBeanDTO.class).addMappings(mapper -> {
        mapper.map(src -> src.getValue(), (dest, value) -> dest.setValue(modelMapper.map(value, listType)));
});

And I get below error in this case:

java.lang.IllegalArgumentException: source cannot be null
    at org.modelmapper.internal.util.Assert.notNull(Assert.java:53)
    at org.modelmapper.ModelMapper.map(ModelMapper.java:493)
    at com.lowteq.model.preop.controller.config.BeanModelMapperFactory.lambda$22(BeanModelMapperFactory.java:117)
    at org.modelmapper.internal.ReferenceMapExpressionImpl.map(ReferenceMapExpressionImpl.java:68)
    at org.modelmapper.internal.ConfigurableConditionExpressionImpl.map(ConfigurableConditionExpressionImpl.java:65)
    at com.lowteq.model.preop.controller.config.BeanModelMapperFactory.lambda$20(BeanModelMapperFactory.java:117)
    at org.modelmapper.internal.TypeMapImpl.addMappings(TypeMapImpl.java:266)
Daffy
  • 140
  • 2
  • 13
  • 1
    Error says: `List` is an interface and it doesn't have any constructor. You should try using List implementation, e.g. ArrayList. `ItemsBeanDTO extends AbstractBeanDTO>` – Nikolai Shevchenko Dec 22 '20 at 08:19
  • 1
    @NikolaiShevchenko: While this might work, it's usually not a best practice to use implementation classes in declarations. – Puce Dec 22 '20 at 08:33
  • I tried this, but then realized it wont do good for the hibernate part. – Daffy Dec 22 '20 at 08:50

3 Answers3

2

List is an interface and doesn't have a constructor. ModelMapper requires a concrete class, therefore you need to prefer an implementation of List including ArrayList or LinkedList etc.

Berkay
  • 1,029
  • 1
  • 10
  • 28
2

Finally I found a solution for this problem. You can use typeMap to skip the setter method setting the List and set it manually using setProvider.

TypeMap<ItemsBean, ItemsBeanDTO> typeMap = modelMapper.createTypeMap(ItemsBean.class,
                ItemsBeanDTO.class);

        typeMap.addMappings(mapper -> mapper.skip(ItemsBeanDTO::set));

        typeMap.setProvider(request -> {
            ItemsBean source = ItemsBean.class.cast(request.getSource());
            ItemsBeanDTO destination = new ItemsBeanDTO();
            destination.set(source.get());
            return destination;
        });
Daffy
  • 140
  • 2
  • 13
0

You can use my library beanknife instead. It is a annotation processor. It means it generate the DTO class on the fly. You can check the source of the generated class, no more magic. The converter methods are embedded into the generated class. They are not complex, but they are cumbersome, so automation is necessary. You can filter the properties inherited from the original class, you can even change or add new property to the generated class. The library provide a way to convert the property just by an annotation. For example convert the Integer to zero if it is null. You can also write your own implementation.

For example:

class Pojo1 {
    String a;
    Pojo b;
}

class Pojo2 {
    Pojo1 a;
    List<Pojo1> b;
    Map<List<Pojo1>>[] c;
}

// target Pojo1, and all properties will be included except 'b'
@ViewOf(value = Pojo1.class, includePattern = ".*", excludes={Pojo1Meta.b})
class ConfigureOfPojo2 {}

// target Pojo2, and all properties will be included
@ViewOf(value = Pojo2.class, includePattern = ".*")
class ConfigureOfPojo2 {
    // convert b to dto version. 
    // Pojo1View is the generated dto class of Pojo1.
    // Of course, you can change it. But here use the default name for simpify.
    // If you want change the type or value of a existing property,
    // You should use @OverrideViewProperty.
    // Convert to dto version is internal supported.
    // For more complex case, you may need to use a method to define how the property should be deal with.
    @OverrideViewProperty(Pojo2Meta.b)
    private List<Pojo1View> b;

    // Convert c to dto version
    // BeanKnife support convert list, set, map, array of object to its dto version, only if they has the same shape.
    @OverrideViewProperty(Pojo2Meta.c)
    private Map<List<Pojo1View>>[] c;
}

will generate

// meta class, you can use it to reference the property name in a safe way.
class Pojo1Meta {
    public final String a = "a";
    public final String b = "b";
}

// generated DTO class. The actual one will be more complicate, there are many other method.
class Pojo1View {
    private String a;
    public Pojo1View read(Pojo1 source) { ... }
    ... getters and setters ...
}

class Pojo2Meta {
    public final String a = "a";
    public final String b = "b";
    public final String c = "c";
}

class Pojo2View {
    private String a;
    private List<Pojo1View> b;
    private Map<List<Pojo1View>>[] c;
    public Pojo2View read(Pojo2 source) { ... }
    ... getters and setters ...
}

Here are more examples

vipcxj
  • 840
  • 5
  • 10