You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用MapStruct智能映射JSON返回结果到自定义DTO

优雅实现方案

1. 修正反序列化DTO

原定义的Wrapper类字段类型和实际JSON结构不匹配:JSON中entities是二维数组结构,因此需要先修正字段类型,保证WebClient集成的Jackson能正常反序列化响应。

// 修正后的外层响应包装类
public class Wrapper {
    private int error;
    private String message;
    private int totalItems;
    // 原List<PairEntity>类型错误,改为二维List匹配JSON结构
    private List<List<PairEntity>> entities;

    // 补全全字段getter/setter、无参构造,开发中可直接用Lombok @Data注解简化
}

// 键值对实体类保持原有定义即可
public class PairEntity {
    private String entityName;
    private String entityValue;

    // 同上补全getter/setter、无参构造
}

2. 定义目标业务DTO

按需求定义接收转换结果的DTO:

import java.util.Date;

public class BusinessFieldDto {
    private String field1;
    private Date field2;

    // 补全getter/setter、无参构造
}

3. 编写MapStruct映射接口

利用MapStruct的@Named和qualifiedByName特性,封装按字段名取值、类型转换的逻辑,无需在业务代码中手动遍历键值对集合,所有映射逻辑统一收敛在Mapper中,编译期自动生成实现类,无反射性能损耗。

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

// 指定componentModel = spring,直接将Mapper注册为Spring Bean,可直接@Autowired注入使用
@Mapper(componentModel = "spring")
public interface ExternalResponseMapper {

    /**
     * 单条结果转换入口,适配totalItems=1的场景
     */
    default BusinessFieldDto toSingleDto(Wrapper wrapper) {
        if (wrapper == null || wrapper.getEntities() == null || wrapper.getEntities().isEmpty()) {
            return null;
        }
        // 取第一条数据的键值对集合做映射
        return pairListToDto(wrapper.getEntities().get(0));
    }

    /**
     * 批量结果转换入口,适配totalItems>1的场景
     */
    default List<BusinessFieldDto> toDtoList(Wrapper wrapper) {
        List<BusinessFieldDto> result = new ArrayList<>();
        if (wrapper == null || wrapper.getEntities() == null || wrapper.getEntities().isEmpty()) {
            return result;
        }
        for (List<PairEntity> pairList : wrapper.getEntities()) {
            result.add(pairListToDto(pairList));
        }
        return result;
    }

    // 配置字段映射规则
    @Mapping(target = "field1", source = "pairList", qualifiedByName = "extractField1")
    @Mapping(target = "field2", source = "pairList", qualifiedByName = "extractField2AsDate")
    BusinessFieldDto pairListToDto(List<PairEntity> pairList);

    // 提取Field1对应的字符串值
    @Named("extractField1")
    default String extractField1(List<PairEntity> pairList) {
        return pairList.stream()
                .filter(pair -> "Field 1".equals(pair.getEntityName()))
                .findFirst()
                .map(PairEntity::getEntityValue)
                .orElse(null);
    }

    // 提取Field2对应的值并转换为指定格式的Date
    @Named("extractField2AsDate")
    default Date extractField2AsDate(List<PairEntity> pairList) {
        String dateValue = pairList.stream()
                .filter(pair -> "Field 2".equals(pair.getEntityName()))
                .findFirst()
                .map(PairEntity::getEntityValue)
                .orElse(null);
        if (dateValue == null) {
            return null;
        }
        // 格式提示:dd-mm-yyyy中mm代表分钟,若要解析月份请使用大写MM,即dd-MM-yyyy,可根据实际业务调整
        SimpleDateFormat dateFormat = new SimpleDateFormat("dd-mm-yyyy");
        try {
            return dateFormat.parse(dateValue);
        } catch (ParseException e) {
            // 按需处理解析异常,可抛自定义业务异常、记录日志或返回null
            throw new IllegalArgumentException("Field2日期格式非法: " + dateValue, e);
        }
    }
}

使用方式

直接在Service层注入Mapper,调用对应转换方法即可:

@Service
public class ExternalCallService {
    @Autowired
    private ExternalResponseMapper responseMapper;
    @Autowired
    private WebClient webClient;

    public BusinessFieldDto callExternalService() {
        Wrapper response = webClient.get()
                .uri("外部接口地址")
                .retrieve()
                .bodyToMono(Wrapper.class)
                .block();
        // 直接转换,无需手动遍历键值对
        return responseMapper.toSingleDto(response);
    }
}

方案优势

  • 无冗余手动遍历逻辑:所有键值对匹配、转换逻辑都由MapStruct编译期生成实现,业务代码中不需要写循环遍历entities的逻辑
  • 易维护:后续新增映射字段,只需要在Mapper中新增对应@Mapping规则和取值方法即可,不需要修改公共遍历代码
  • 高性能:编译期生成纯Java代码赋值,和手写硬编码逻辑性能一致,无反射开销
  • 低侵入:适配Spring生态,直接注入即可使用

注意:SimpleDateFormat是线程不安全类,高并发场景建议将field2类型改为Java 8+提供的LocalDate,使用线程安全的DateTimeFormatter做日期转换,避免并发问题。


内容的提问来源于stack exchange,提问作者user1436883

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 10:18:05