TL;DR
I want to use modelMapper in a way that I map from AbstractParent to AbstractParentDTO and later in the ModelMapper-Config call the specific mappers for each Sub-class and then skip the rest of the (abstrac-class) mappings.
How is that Possible? Is this the right approach? Is there a design flaw?
What I have:
The parent entity:
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public abstract class Parent {
//some more fields
}
One child entity:
//Basic Lombok Annotations
@DiscriminatorValue("child_a")
public class ChildA extends Parent {
//some more fields
}
Another child entity:
@DiscriminatorValue("child_b")
public class ChildB extends Parent {
//some more fields
}
Then I have the parent DTO class:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildA.class, name = "child_a"),
@JsonSubTypes.Type(value = ChildB.class, name = "child_b"),
public abstract class ParentDTO {
//some more fields
}
One Child DTO:
public class ClassADTO extends ParentDTO {
//some more fields
}
and another DTO:
public class ClassBDTO extends ParentDTO {
//some more fields
}
In my case I'll get DTO's from the controller and map them to Entities when giving them to the Service. I'll have to do the same thing in 5-6 Endpoints.
The Endpoints look roughly like this:
@PreAuthorize(CAN_WRITE)
@PutMapping("/{id}")
public ResponseEntity<ParentDTO> update(
@PathVariable("id") UUID id,
@RequestBody @Valid ParentDTO parentDTO) {
Parent parent = parentService.update(id, parentDTO);
if (parentDTO instanceof ChildADTO) {
return ResponseEntity.ok(modelMapper.map(parent, ChildADTO.class));
} else if (parentDTO instanceof ChildBDTO) {
return ResponseEntity.ok(modelMapper.map(parent, ChildBDTO.class));
}
throw new BadRequestException("The Parent is not Valid");
}
Only that I have a few more Childs that make things even bulkier.
What I want:
Instead of checking a bunch of times what instance the DTO (or Entity) is, I simply want to write for example:
modelmapper.map(parent, ParentDTO.class)
and do the "instance of..." check ONCE in my ModelMapper Configuration.
What I've tried:
I already have different Converters for every possible direction and mapping-case defined in my ModelMapper Configuration (since they require more complex mapping anyways).
I've tried to solve my problem by writing one more Converter for the Parent Classes and setting it as a ModelMapper PreConverter:
//from Entity to DTO
Converter<Parent, ParentDTO> parentParentDTOConverter = mappingContext -> {
Parent source = mappingContext.getSource();
ParentDTO dest = mappingContext.getDestination();
if (source instanceof CHildA) {
return modelMapper.map(dest, ChildADTO.class);
} else if (source instanceof ChildB) {
return modelMapper.map(dest, ChildBDTO.class);
}
return null;
};
and:
modelMapper.createTypeMap(Parent.class, ParentDTO.class)
.setPreConverter(parentParentDTOConverter);
But I'm always getting the same MappingError:
1) Failed to instantiate instance of destination com.myexample.data.dto.ParentDTO. Ensure that com.myexample.data.dto.ParentDTOO has a non-private no-argument constructor.
which I get (I guess), I cannot construct an Object of an abstract class. But thats not what I'm trying, am I? I guess that modelMapper is still doing the rest of the Mapping after finishing with my PreConverter. I've also tried to set it with .setConverter but always with the same result.
Does anyone knows how to 'disable' the custom mappings? I don't really want to write "pseudo-mappers" that act like mappers and just call the specific mappers for each scenario.
Is my design just bad? How would you improve it?
Is this just not implemented into ModelMapper yet?
Any help and hint is appreciated.
I would use ObjectMapper instead of ModelMapper.
In Parent class add the possibility to get the discriminator value.
//..
public class Parent {
@Column(name = "type", insertable = false, updatable = false)
private String type;
//getters and setters
}
Your ParentDTO should be mapped to Child(*)DTO
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildADTO.class, name = "child_a"),
@JsonSubTypes.Type(value = ChildBDTO.class, name = "child_b")
})
public abstract class ParentDTO {
// ..
}
in the conversion service/method add an object mapper with ignore unknown (to ignore what you did not declare in your DTO class)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
just simply call :
Parent parent = // get from repository
ParentDTO parentDTO = objectMapper.readValue(objectMapper.writeValueAsBytes(parent), ParentDTO.class);
In this way, your ParentDTO is always instantiated with the right type.
Well, the solution I found uses converters. In this case modelMapper
doesn't try to create a new instance of abstract class, but uses the converter directly.
You can put all the converters in same place
modelMapper.createTypeMap(ChildA.class, ParentDTO.class)
.setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassADTO.class));
modelMapper.createTypeMap(ChildB.class, ParentDTO.class)
.setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassBDTO.class));
....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With