I use ModelMapper to convert Models to DTOs. I have a bunch of default converters for null values that have been registered at the mapper level like this:
modelMapper.addConverter(new Converter<String, String>() {
@Override
public String convert(MappingContext<String, String> context) {
if (context.getSource() == null) {
return "global null converter was here";
}
return context.getSource();
}
});
This works fine with simple mapping when the properties name are the same on both side of the conversion. The converter is used to handle null values as expected.
Now if I need to do a more complex conversions with different properties name by using .map(getter, setter) on the type map, the global converters are not called anymore. I don't want the global converters to be discarded when configuring the typemap.
How can I fix that ?
Here is a sample code (with lombok for code brevity) that use ModelMapper 2.3.8, the today's latest version:
@Data @AllArgsConstructor @NoArgsConstructor class A { String a; String b;}
@Data @AllArgsConstructor @NoArgsConstructor class B { String a; String b; }
@Data @AllArgsConstructor @NoArgsConstructor class C { String x; String y;}
public class MapperTestCase {
public static void main(String[] args) throws IOException {
A a = new A("aaa", "bbb");
ModelMapper modelMapper = new ModelMapper();
final TypeMap<A, B> AtoBTypeMap = modelMapper.createTypeMap(A.class, B.class);
B b = AtoBTypeMap.map(a);
System.out.println("conversion with no converter A -> B: " + a + " -> " + b);
a = new A(null, null);
b = AtoBTypeMap.map(a);
System.out.println("conversion with no converter A -> B: " + a + " -> " + b);
// Add a global/fallback converter that should convert all null String values.
modelMapper.addConverter(new Converter<String, String>() {
@Override
public String convert(MappingContext<String, String> context) {
if (context.getSource() == null) {
return "global null converter was here";
}
return context.getSource();
}
});
final TypeMap<B, A> BtoATypeMap = modelMapper.typeMap(B.class, A.class);
a = BtoATypeMap.map(b);
System.out.println("conversion with global converter B -> A: " + b + " -> " + a);
// add a local converter for the B to C type mape only
BtoATypeMap.addMappings(mapper -> mapper.using(ctx -> {
if (ctx.getSource() == null) {
return "local converter was here";
} else return ctx.getSource();
}).map(B::getA, (w, x) -> w.setA(String.valueOf(x))));
// in this conversion both converter (global and local) should be used
a = BtoATypeMap.map(b);
System.out.println("conversion with global and local converter B -> A: " + b + " -> " + a);
// a new typeMap that will transform a B into a C, mapping B::a to C::x and B::b to C::y
final TypeMap<B, C> BtoCTypeMap = modelMapper.typeMap(B.class, C.class);
// a local converter for this type map
BtoCTypeMap.addMappings(mapper -> mapper.using(ctx -> {
if (ctx.getSource() == null) {
return "local converter was here";
} else return ctx.getSource();
}).map(B::getA, (w, x) -> w.setX(String.valueOf(x))));
BtoCTypeMap.addMapping(B::getB, C::setY);
// first a conversion with a B instance without null values, works as expected
b = new B("some", "data");
C c = BtoCTypeMap.map(b);
System.out.println("conversion with global and local converter B -> C: " + b + " -> " + c);
// now a conversion with a B instance wirth null values, the local converer will be used, but not the global one defined at the mapper level. Why ?
b = new B();
c = BtoCTypeMap.map(b);
System.out.println("conversion with global and local converter B -> C: " + b + " -> " + c);
}
}
The output is:
conversion with no converter A -> B: A(a=aaa, b=bbb) -> B(a=aaa, b=bbb)
conversion with no converter A -> B: A(a=null, b=null) -> B(a=null, b=null)
conversion with global converter B -> A: B(a=null, b=null) -> A(a=global null converter was here, b=global null converter was here)
conversion with global and local converter B -> A: B(a=null, b=null) -> A(a=local converter was here, b=global null converter was here)
conversion with global and local converter B -> C: B(a=some, b=data) -> C(x=some, y=data)
conversion with global and local converter B -> C: B(a=null, b=null) -> C(x=local converter was here, y=null)
The expected output for the last line is C(x=local converter was here, y=global null converter was here)