如何通过Feign客户端映射嵌套JSON中的List对象?
解决Feign调用API时嵌套List的反序列化问题
错误原因
你遇到的Cannot deserialize value of type java.util.ArrayList<> from Object value错误,核心原因是:
- API返回的是完整的JSON对象(顶层包含
apiVersion、transactionId、data等字段) - 但你的Feign方法直接声明返回
List<AgendamentoResponse>,Jackson尝试将整个顶层JSON对象转换为List,类型不匹配导致失败 - 同时你原来的
@JsonSetter路径data.workschedule.scheduleDate无效,因为workschedule是一个List,而非单个对象,无法通过该路径定位到列表元素的字段
最优解决方案:定义顶层响应DTO
最直接且易维护的方式是创建一个顶层DTO,映射API返回的完整结构,再从中提取需要的List。
1. 创建顶层响应DTO
public record AgendamentoApiResponse( String apiVersion, String transactionId, Data data ) { // 嵌套内部类,映射data字段的结构 public record Data( List<AgendamentoResponse> workschedule ) {} }
2. 调整AgendamentoResponse的字段映射
去掉原来路径中的data.workschedule.,直接映射列表元素的字段:
public record AgendamentoResponse( @DatePattern @JsonSetter("scheduleDate") LocalDate dataAgendamento, @JsonSetter("timeslotDescription") String periodoLabel, @JsonSetter("timeslotStart") String periodoInicio, @JsonSetter("timeslotEnd") String periodoFim, @JsonSetter("category") String categoria ) {}
3. 修改Feign客户端的返回类型
将返回类型改为顶层DTO,之后从DTO中提取List:
@GetMapping("appointments/workschedules") AgendamentoApiResponse consultarAgendamentosDisponiveis( @RequestHeader(name = "X-QueryString", required = false) String partyAccountId, @SpringQueryMap AgendamentoParams params);
4. 调用示例
// 调用Feign接口 AgendamentoApiResponse apiResponse = feignClient.consultarAgendamentosDisponiveis(partyAccountId, params); // 提取需要的List List<AgendamentoResponse> agendamentos = apiResponse.data().workschedule();
可选方案:自定义Feign Decoder(进阶)
如果不想创建顶层DTO,可以自定义Decoder,直接从JSON中提取data.workschedule路径下的List:
@Component public class CustomFeignDecoder extends SpringDecoder { private final ObjectMapper objectMapper; public CustomFeignDecoder(ObjectMapper objectMapper) { super(new ObjectFactory<HttpMessageConverter<?>>() { @Override public HttpMessageConverter<?> getObject() throws BeansException { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(objectMapper); return converter; } }); this.objectMapper = objectMapper; } @Override public Object decode(Response response, Type type) throws IOException, FeignException { if (type instanceof ParameterizedType parameterizedType && parameterizedType.getRawType().equals(List.class)) { // 直接读取data.workschedule路径下的List JsonNode rootNode = objectMapper.readTree(response.body().asInputStream()); JsonNode workscheduleNode = rootNode.path("data").path("workschedule"); return objectMapper.treeToValue(workscheduleNode, type); } return super.decode(response, type); } }
然后在Feign客户端配置中指定这个Decoder:
@FeignClient(name = "agendamentoClient", url = "${api.url}", configuration = {CustomFeignDecoder.class}) public interface AgendamentoFeignClient { // 方法保持原来的List返回类型 @GetMapping("appointments/workschedules") List<AgendamentoResponse> consultarAgendamentosDisponiveis( @RequestHeader(name = "X-QueryString", required = false) String partyAccountId, @SpringQueryMap AgendamentoParams params); }
注意:这种方式适合需要频繁跳过顶层字段的场景,但会增加代码复杂度,建议优先使用顶层DTO方案。
内容的提问来源于stack exchange,提问作者João Victor Afonso
相关产品推荐
相关产品推荐

