ModelMapper:根据Childclass选择映射
ModelMapper: Choose mapping based on Child class
TL;DR
我想以从 AbstractParent 映射到 AbstractParentDTO 并稍后在 ModelMapper-Config 中为每个 [=101= 调用特定映射器的方式使用 modelMapper ] 然后跳过其余的 (abstrac-class) 映射。
这怎么可能?这是正确的方法吗?是否存在设计缺陷?
我有:
parent实体:
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public abstract class Parent {
//some more fields
}
一个 child 实体:
//Basic Lombok Annotations
@DiscriminatorValue("child_a")
public class ChildA extends Parent {
//some more fields
}
另一个 child 实体:
@DiscriminatorValue("child_b")
public class ChildB extends Parent {
//some more fields
}
然后我有 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
}
一个 Child DTO:
public class ClassADTO extends ParentDTO {
//some more fields
}
和另一个 DTO:
public class ClassBDTO extends ParentDTO {
//some more fields
}
在我的例子中,我将从控制器获取 DTO,并在将它们提供给服务时将它们映射到实体。我将不得不在 5-6 个端点中做同样的事情。
端点大致如下所示:
@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");
}
只是我还有一些 Child 让事情变得更笨重。
我想要的:
我不想多次检查 DTO(或实体)是什么实例,我只想写例如:
modelmapper.map(parent, ParentDTO.class)
并在我的 ModelMapper 配置中执行 "instance of..." 检查一次。
我试过的:
我已经为每个可能的方向设置了不同的转换器,并且 mapping-case 在我的 ModelMapper 配置中定义(因为它们无论如何都需要更复杂的映射)。
我试图通过为 Parent 类 编写一个转换器并将其设置为 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;
};
和:
modelMapper.createTypeMap(Parent.class, ParentDTO.class)
.setPreConverter(parentParentDTOConverter);
但我总是得到相同的 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.
据我所知(我猜),我无法构建摘要 class 的 Object。但这不是我正在尝试的,是吗?
我猜 modelMapper 在完成我的 PreConverter 后仍在做其余的映射。我也尝试用 .setConverter 设置它,但结果总是一样。
有谁知道如何 'disable' 自定义映射?我不
真的很想写 "pseudo-mappers" 就像映射器一样
为每个场景调用特定的映射器。
是我的设计不好吗?你会如何改进它?
这还没有实现到 ModelMapper 中吗?
感谢任何帮助和提示。
好吧,我找到的解决方案是使用转换器。在这种情况下 modelMapper
不会尝试创建抽象的新实例 class,而是直接使用转换器。
你可以把所有的转换器放在同一个地方
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));
....
我会使用 ObjectMapper 而不是 ModelMapper。
在Parent class中添加获取鉴别器值的可能性。
//..
public class Parent {
@Column(name = "type", insertable = false, updatable = false)
private String type;
//getters and setters
}
您的 ParentDTO 应该映射到 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 {
// ..
}
在转换中 service/method 添加一个忽略未知的对象映射器(忽略您未在 DTO 中声明的内容 class)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
只需调用 :
Parent parent = // get from repository
ParentDTO parentDTO = objectMapper.readValue(objectMapper.writeValueAsBytes(parent), ParentDTO.class);
通过这种方式,您的 ParentDTO 总是以正确的类型实例化。
怎么样
TypeMap<Parent.class, ParentDTO.class> typeMap = modelMapper.createTypeMap(Parent.class, ParentDTO.class);
typeMap
.include(ChildA .class, ClassADTO .class)
.include(ChildB.class, ClassbDTO.class);
TL;DR
我想以从 AbstractParent 映射到 AbstractParentDTO 并稍后在 ModelMapper-Config 中为每个 [=101= 调用特定映射器的方式使用 modelMapper ] 然后跳过其余的 (abstrac-class) 映射。
这怎么可能?这是正确的方法吗?是否存在设计缺陷?
我有:
parent实体:
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public abstract class Parent {
//some more fields
}
一个 child 实体:
//Basic Lombok Annotations
@DiscriminatorValue("child_a")
public class ChildA extends Parent {
//some more fields
}
另一个 child 实体:
@DiscriminatorValue("child_b")
public class ChildB extends Parent {
//some more fields
}
然后我有 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
}
一个 Child DTO:
public class ClassADTO extends ParentDTO {
//some more fields
}
和另一个 DTO:
public class ClassBDTO extends ParentDTO {
//some more fields
}
在我的例子中,我将从控制器获取 DTO,并在将它们提供给服务时将它们映射到实体。我将不得不在 5-6 个端点中做同样的事情。
端点大致如下所示:
@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");
}
只是我还有一些 Child 让事情变得更笨重。
我想要的:
我不想多次检查 DTO(或实体)是什么实例,我只想写例如:
modelmapper.map(parent, ParentDTO.class)
并在我的 ModelMapper 配置中执行 "instance of..." 检查一次。
我试过的:
我已经为每个可能的方向设置了不同的转换器,并且 mapping-case 在我的 ModelMapper 配置中定义(因为它们无论如何都需要更复杂的映射)。
我试图通过为 Parent 类 编写一个转换器并将其设置为 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;
};
和:
modelMapper.createTypeMap(Parent.class, ParentDTO.class)
.setPreConverter(parentParentDTOConverter);
但我总是得到相同的 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.
据我所知(我猜),我无法构建摘要 class 的 Object。但这不是我正在尝试的,是吗? 我猜 modelMapper 在完成我的 PreConverter 后仍在做其余的映射。我也尝试用 .setConverter 设置它,但结果总是一样。
有谁知道如何 'disable' 自定义映射?我不 真的很想写 "pseudo-mappers" 就像映射器一样 为每个场景调用特定的映射器。
是我的设计不好吗?你会如何改进它?
这还没有实现到 ModelMapper 中吗?
感谢任何帮助和提示。
好吧,我找到的解决方案是使用转换器。在这种情况下 modelMapper
不会尝试创建抽象的新实例 class,而是直接使用转换器。
你可以把所有的转换器放在同一个地方
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));
....
我会使用 ObjectMapper 而不是 ModelMapper。
在Parent class中添加获取鉴别器值的可能性。
//..
public class Parent {
@Column(name = "type", insertable = false, updatable = false)
private String type;
//getters and setters
}
您的 ParentDTO 应该映射到 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 {
// ..
}
在转换中 service/method 添加一个忽略未知的对象映射器(忽略您未在 DTO 中声明的内容 class)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
只需调用 :
Parent parent = // get from repository
ParentDTO parentDTO = objectMapper.readValue(objectMapper.writeValueAsBytes(parent), ParentDTO.class);
通过这种方式,您的 ParentDTO 总是以正确的类型实例化。
怎么样
TypeMap<Parent.class, ParentDTO.class> typeMap = modelMapper.createTypeMap(Parent.class, ParentDTO.class);
typeMap
.include(ChildA .class, ClassADTO .class)
.include(ChildB.class, ClassbDTO.class);