I've been using modelmapper and java 8 Optionals all around the application which was working fine because they were primitive types; until I changed one of my model objects' field to Optional type. Then all hell broke loose. Turns out many libraries cannot handle generics very well.
Here is the structure
public class MyObjectDto
{
private Optional<MySubObjectDto> mySubObject;
}
public MyObject
{
privae Optional<MySubjObject> mySubObject;
}
When I attempt to map MyObjectDto
to MyObject
, modelmapper calls
public void setMySubObject(Optional<MySubObject> mySubObject){
this.mySubObject = mySubObject;
}
with Optional<MySubObjectDto>
, which I don't understand how that's even possible (there is no inheritance between them). Of course that crashes fast. For now I've changed my setters to accept Dto type just to survive the day but that's not going to work on the long run. Is there a better way to get around this, or shall I create an issue?
So I digged into the modelmapper code and have done this looking at some generic implementations:
modelMapper.createTypeMap(Optional.class, Optional.class).setConverter(new OptionalConverter());
public class OptionalConverter implements ConditionalConverter<Optional, Optional> {
public MatchResult match(Class<?> sourceType, Class<?> destinationType) {
if (Optional.class.isAssignableFrom(destinationType)) {
return MatchResult.FULL;
} else {
return MatchResult.NONE;
}
}
private Class<?> getElementType(MappingContext<Optional, Optional> context) {
Mapping mapping = context.getMapping();
if (mapping instanceof PropertyMapping) {
PropertyInfo destInfo = ((PropertyMapping) mapping).getLastDestinationProperty();
Class<?> elementType = TypeResolver.resolveArgument(destInfo.getGenericType(),
destInfo.getInitialType());
return elementType == TypeResolver.Unknown.class ? Object.class : elementType;
} else if (context.getGenericDestinationType() instanceof ParameterizedType) {
return Types.rawTypeFor(((ParameterizedType) context.getGenericDestinationType()).getActualTypeArguments()[0]);
}
return Object.class;
}
public Optional<?> convert(MappingContext<Optional, Optional> context) {
Class<?> optionalType = getElementType(context);
Optional source = context.getSource();
Object dest = null;
if (source != null && source.isPresent()) {
MappingContext<?, ?> optionalContext = context.create(source.get(), optionalType);
dest = context.getMappingEngine().map(optionalContext);
}
return Optional.ofNullable(dest);
}
}