I'm using ModelMapper to convert data between some apis.
Some of inner fields in api A are String and need to be converted to inner fields of B that are either Integer, Long, Short, Double or Byte.
Is it possible to create 1 single converter that would be applied to all fields?
If I do:
modelMapper.addConverter(new Converter<String, Integer>() {
@Override
public Integer convert(MappingContext<String, Integer> context) {
if (StringUtils.isBlank(context.getSource())) {
return null;
}
long value = Long.parseLong(context.getSource().replaceAll("\\D", ""));
return value > Integer.MAX_VALUE || value < Integer.MIN_VALUE ? null : (int) value;
}
});
modelMapper.addConverter(new Converter<String, Long>() {
@Override
public Long convert(MappingContext<String, Long> context) {
if (StringUtils.isBlank(context.getSource())) {
return null;
}
return Long.parseLong(context.getSource().replaceAll("\\D", ""));
}
});
modelMapper.addConverter(new Converter<String, Double>() {
@Override
public Double convert(MappingContext<String, Double> context) {
if (StringUtils.isBlank(context.getSource())) {
return null;
}
return Double.parseDouble(context.getSource().replaceAll("^[\\d.]", ""));
}
});
this works as I expect but it is a lot of code repetition.
But if I do:
modelMapper.addConverter(new Converter<String, Number>() {
@Override
public Numberconvert(MappingContext<String, Number> context) {
if (StringUtils.isBlank(context.getSource())) {
return null;
}
return Double.parseDouble(context.getSource().replaceAll("^[\\d.]", ""));
}
});
this converter won't even be called during the data transformation.
Besides the string -> number transformation, I also need transformation from int -> short, long -> int, long -> short, etc.
So in the end I would have around 1000 lines just of these boilerplate repetition code...
"Ah, but there is default converters on modelmapper you don't need to redefine them" - Modelmapper converters won't work for me because they throw exception on any small data problem [such as int value is to big for short], and I need to simply ignore those errors and place null.
How to achieve this?
Having one 'parent' converter, that handles everything, would make it something like a 'god' converter - doing too many things and having too much responsibilities. Not to mention way too much code in one place.
I would suggest the template method pattern to avoid the duplication:
This is taking advantage of the fact that
Number.parseNumbermethods throwNumberFormatException, if the string represents a number, which does not fit in the range of the type.You can extend this base class for all number types and register manually all converters, or you can follow the dynamic registration example bellow.
Since there can be a lot of number -> number combinations, you can register the converters dynamically.
DestinationsHolderis a class with the purpose to disallow incorrect class to parser function mappings -Integer.class->Double::parseDoublewill result in compilation error.Unit tests: