I retrieve proxy objects from the database (using Spring Data neo4j) using an interface projection. Then I map those proxy objects to DTOs using ModelMapper. All interfaces and classes implement the same BaseInterface that has a getter and setter for the id attribute.
public interface BaseInterface {
Long getId();
void setId(Long id);
Boolean getVisible();
void setVisible(Boolean visible);
}
I am mapping like this inside my abstract generic service class:
repository.findAll(statement, interfaceType).stream().map(proxy -> modelMapper.map(proxy, dtoType));
The mapping for all attributes from the BaseInterface works (I have included "visible" in this example), also mapping for the attributes of the subclasses does work. But ModelMapper never maps the id attribute, even when explicitly configured:
var modelMapper = new ModelMapper();
PropertyMap<BaseInterface, BaseNode> basePropertyMap = new PropertyMap<BaseInterface, BaseNode>() {
@Override
protected void configure() {
map().setId(source.getId());
}
};
modelMapper.addMappings(basePropertyMap);
I also tried:
TypeMap<BaseInterface, BaseNode> baseTypeMap = modelMapper.createTypeMap(BaseInterface.class, BaseNode.class);
baseTypeMap.addMapping(BaseInterface::getId, BaseNode::setId);
I have come around to a really terrible hack, that works.
repository.findAll(statement, interfaceType).stream().map(proxy -> {
var theMappedObject = modelMapper.map(proxy, dtoType);
var id = ((BaseInterface) proxy).getId();
((BaseNode) theMappedObject).setId(id);
return theMappedObject;
});
What also works, is explicitly mapping for the subclasses, but this creates a lot of boilerplate code - I'd rather go with the hack above than this:
TypeMap<PersonListInterface, PersonListDTO> baseTypeMap = modelMapper.createTypeMap(PersonListInterface.class, PersonListDTO.class);
baseTypeMap.addMapping(BaseInterface::getId, BaseInterface::setId);
Why does ModelMapper skip the id attribute and how can I make it not skip it? I'm using ModelMapper v3.1.1.
The answer is: ModelMapper does not seem to work well with interfaces extending other interfaces. What at first I did not see is that when mapping the proxy to the DTO, ModelMapper mapped attributes from the base interface, except ID, but did not map attributes of subinterfaces correctly. So the behaviour of ModelMapper in these cases seems erratic, because I can't really tell whicht fields are going to be mapped (and why) and which arent. After explicitly putting all the getters and setters in all interface it works.
For example:
public interface BaseInterface {
Long getId();
void setId(Long id);
Boolean getDeleted();
void setDeleted(Boolean deleted);
}
public interface PersonListInterface extends BaseInterface {
Long getId();
void setId(Long id);
Boolean getDeleted();
void setDeleted(Boolean deleted);
String getName();
}
public interface PersonEditInterface extends PersonListInterface {
Long getId();
void setId(Long id);
Boolean getDeleted();
void setDeleted(Boolean deleted);
String getName();
void setName(String name);
}
public class PersonEditDTO implements PersonEditInterface {
// Regular DTO implementation
}