如何使用ModelMapper处理深度继承对象并保留type字段?
这个问题我之前也碰到过,核心原因很明确:@JsonTypeInfo是Jackson用来序列化/反序列化时识别子类的注解,它生成的type字段是Jackson动态注入的,并不是你的实体类A/AA/AB中定义的显式属性。而ModelMapper的映射逻辑是基于实体类的getter/setter或者字段来工作的,所以默认情况下它不会把这个Jackson生成的type字段映射到DTO里。
下面给你几个实用的解决方案,你可以根据自己的代码场景选择:
方案一:给抽象实体类添加显式的type属性(推荐)
这种方式最优雅,既符合Jackson的序列化逻辑,也能让ModelMapper正常识别映射,同时不会影响JPA的实体映射。
首先在抽象父类A中添加一个抽象的getType()方法,并用@JsonProperty标记,让Jackson也能识别这个方法返回值作为type字段:
@MappedSuperclass // 或者根据你的实际情况用@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) // 这里假设是单表继承,按需调整 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = AA.class, name = "AA"), @JsonSubTypes.Type(value = AB.class, name = "AB") }) public abstract class A { // 你的其他实体属性... // 显式定义type的getter,由子类实现返回对应的类型标识 @JsonProperty("type") public abstract String getType(); }
然后在子类AA、AB中实现这个方法,返回对应的类型名称:
@Entity public class AA extends A { private User user; @Override public String getType() { return "AA"; // 和@JsonSubTypes中定义的name一致 } // User属性的getter/setter... } @Entity public class AB extends A { private String name; @Override public String getType() { return "AB"; // 和@JsonSubTypes中定义的name一致 } // name属性的getter/setter... }
接下来给对应的DTO类也添加上type属性和对应的getter/setter:
public abstract class ADto { private String type; public String getType() { return type; } public void setType(String type) { this.type = type; } } public class AADto extends ADto { private UserDto user; // 假设UserDto是User对应的DTO类 // UserDto属性的getter/setter... } public class ABDto extends ADto { private String name; // name属性的getter/setter... }
这样配置后,ModelMapper在映射的时候,就会自动把实体类getType()的返回值映射到DTO的type字段里,完全不需要额外的配置。
方案二:自定义ModelMapper的TypeMap转换器
如果不想修改实体类的代码,可以给ModelMapper配置自定义的类型映射规则,手动设置type字段的值。
示例配置代码如下:
// 初始化ModelMapper ModelMapper modelMapper = new ModelMapper(); // 配置AA到AADto的映射,手动设置type字段 modelMapper.createTypeMap(AA.class, AADto.class) .addMapping(source -> "AA", AADto::setType); // 配置AB到ABDto的映射,手动设置type字段 modelMapper.createTypeMap(AB.class, ABDto.class) .addMapping(source -> "AB", ABDto::setType); // 如果需要支持父类A到ADto的映射,也可以添加通用逻辑 modelMapper.createTypeMap(A.class, ADto.class) .addMapping(source -> { if (source instanceof AA) { return "AA"; } else if (source instanceof AB) { return "AB"; } return null; // 处理其他未知子类的情况 }, ADto::setType);
当你调用modelMapper.map(abInstance, ABDto.class)时,ModelMapper就会按照你配置的规则,把type字段设置为"AB"。
方案三:借助Jackson先序列化再反序列化(应急方案)
如果上面两种方案都不适合,你可以先利用Jackson把实体序列化为包含type字段的JSON字符串,再反序列化为DTO。不过这种方式会绕开ModelMapper的映射逻辑,可能丢失你之前配置的其他映射规则,只适合简单场景:
ObjectMapper objectMapper = new ObjectMapper(); // 先把实体序列化为带type字段的JSON String jsonStr = objectMapper.writeValueAsString(abInstance); // 再把JSON反序列化为DTO ABDto abDto = objectMapper.readValue(jsonStr, ABDto.class);
内容的提问来源于stack exchange,提问作者Morteza Malvandi

