如何通过ModelMapper的addMapping实现RoomType到RoomTypeDto的描述字段自定义映射?
解决ModelMapper中Optional嵌套字段的自定义映射问题
问题背景
在项目中使用ModelMapper进行DTO与实体映射时,尝试为RoomTypeDto定义自定义映射:将RoomType中Optional<Description>类型的description字段,提取其内部的description字符串赋值给RoomTypeDto的同名字段,过程中遇到异常。
错误代码及异常信息
错误代码
var modelMapper = new ModelMapper(); modelMapper.getConfiguration() .setMatchingStrategy(MatchingStrategies.STRICT) .setPreferNestedProperties(false); modelMapper.createTypeMap(RoomType.class, RoomTypeDto.class) .addMapping(rt -> rt.getDescription().map(Description::getDescription).orElse(null), RoomTypeDto::setDescription);
抛出的异常
Caused by: org.modelmapper.internal.ErrorsException: null at org.modelmapper.internal.Errors.toException(Errors.java:253) at org.modelmapper.internal.ReferenceMapExpressionImpl.visitSource(ReferenceMapExpressionImpl.java:113) at org.modelmapper.internal.ReferenceMapExpressionImpl.map(ReferenceMapExpressionImpl.java:66) at org.modelmapper.internal.TypeMapImpl.addMapping(TypeMapImpl.java:260) at pl.ekoncept.room.mapper.RoomTypeReadMapper.<init>(RoomTypeReadMapper.java:20)
可正常运行的跳过代码
modelMapper.createTypeMap(RoomType.class, RoomTypeDto.class) .addMappings(m -> m.skip(RoomTypeDto::setDescription));
实体与DTO结构
RoomTypeDto
@Data @NoArgsConstructor public class RoomTypeDto { private String description; [...] }
RoomType实体
@Entity @Getter @Setter @EqualsAndHashCode(of = "id") @NoArgsConstructor public class RoomType { private Description description; [...] public Optional<Description> getDescription() { return Optional.ofNullable(description); } [...] }
Description实体
@Entity @Getter @Setter @EqualsAndHashCode(of = "id") @NoArgsConstructor public class Description { private String description; [...] }
解决方案
问题根源在于:ModelMapper的addMapping方法要求源表达式是可被解析的属性引用路径,无法直接识别Lambda中的Optional流式操作(map/orElse),会尝试解析该表达式的属性结构导致异常。
可以通过以下两种方式实现正确映射:
方式一:使用Converter转换类型
定义一个Converter处理Optional<Description>到String的转换,再绑定到映射中:
// 定义转换器 Converter<Optional<Description>, String> descriptionConverter = context -> context.getSource().map(Description::getDescription).orElse(null); // 绑定转换器到映射 modelMapper.typeMap(RoomType.class, RoomTypeDto.class) .addMappings(mapper -> mapper.using(descriptionConverter) .map(RoomType::getDescription, RoomTypeDto::setDescription));
方式二:使用PostConverter后置处理
通过setPostConverter在整体转换完成后,手动处理description字段的赋值:
modelMapper.typeMap(RoomType.class, RoomTypeDto.class) .setPostConverter(context -> { RoomType source = context.getSource(); RoomTypeDto destination = context.getDestination(); // 手动处理Optional字段的提取 destination.setDescription(source.getDescription().map(Description::getDescription).orElse(null)); return destination; });
这两种方式都能绕过ModelMapper对属性路径的解析,直接执行自定义的映射逻辑,完成从RoomType.description.description到RoomTypeDto.description的赋值。
内容的提问来源于stack exchange,提问作者Olek
相关产品推荐
相关产品推荐

