Is there a strategy in ModelMapper to map nested object with Optional into a flat structure
Without Optional this works e.g.
source.getRemainingCapacity().getValue()
destination.setRemainingCapacityValue()
but when source.getRemainingCapacity() is a Optional. The mapping is not working
source.getRemainingCapacity().get().getValue()
destination.setRemainingCapacityValue()
Is there a way todo that?
Yes, you can achieve this using a custom property mapping or a converter in ModelMapper to properly handle the Optional
wrapper.
Let’s say you have the following classes:
import java.util.Optional;
public class Source {
private Optional<RemainingCapacity> remainingCapacity;
public Optional<RemainingCapacity> getRemainingCapacity() {
return remainingCapacity;
}
public void setRemainingCapacity(Optional<RemainingCapacity> remainingCapacity) {
this.remainingCapacity = remainingCapacity;
}
public static class RemainingCapacity {
private Integer value;
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
}
public class Destination {
private Integer remainingCapacityValue;
public Integer getRemainingCapacityValue() {
return remainingCapacityValue;
}
public void setRemainingCapacityValue(Integer remainingCapacityValue) {
this.remainingCapacityValue = remainingCapacityValue;
}
}
Trying to access the value directly like this:
source.getRemainingCapacity().get().getValue();
...won’t work with ModelMapper, since it doesn't automatically unwrap Optional
.
PropertyMap
for custom mappingYou can configure a custom mapping that extracts the value from the Optional
:
import org.modelmapper.ModelMapper;
import org.modelmapper.PropertyMap;
public class MappingExample {
public static void main(String[] args) {
ModelMapper modelMapper = new ModelMapper();
modelMapper.addMappings(new PropertyMap<Source, Destination>() {
@Override
protected void configure() {
map().setRemainingCapacityValue(
source.getRemainingCapacity()
.orElse(new Source.RemainingCapacity())
.getValue()
);
}
});
Source source = new Source();
Source.RemainingCapacity capacity = new Source.RemainingCapacity();
capacity.setValue(10);
source.setRemainingCapacity(Optional.of(capacity));
Destination destination = new Destination();
modelMapper.map(source, destination);
System.out.println("Mapped value: " + destination.getRemainingCapacityValue());
}
}
Converter
(more reusable)Using a converter is cleaner, especially if you need to reuse the logic:
import org.modelmapper.Converter;
import org.modelmapper.spi.MappingContext;
Converter<Source, Integer> remainingCapacityConverter = ctx ->
ctx.getSource()
.getRemainingCapacity()
.map(Source.RemainingCapacity::getValue)
.orElse(null);
modelMapper.typeMap(Source.class, Destination.class).addMappings(mapper ->
mapper.using(remainingCapacityConverter)
.map(src -> src, Destination::setRemainingCapacityValue)
);
Use orElse(...)
to avoid NoSuchElementException
, but be mindful that it may still return null
internally.
Prefer Converter
for better testability and modularity.
You can further abstract this logic for multiple Optional
mappings in your project.