如何使用Spring WebClient从JSON响应中提取目标列表数据
问题描述
调用REST API得到的JSON响应如下:
{"_embedded":{"service":[{"id":101,"serviceModelUUID":"aaa8884883","action":"create"},{"id":102,"serviceModelUUID":"aaa8884884","action":"delete"}]}}
需要提取其中的service列表数据:
[{"id":101,"serviceModelUUID":"aaa8884883","action":"create"},{"id":102,"serviceModelUUID":"aaa8884884","action":"delete"}]
但用下面这段Spring WebClient代码调用时,返回的列表里所有字段全是null:
private <T> List<T> getMultipleResources(Class<T> clazz, URI uri) { return webClient.get().uri(uri).retrieve().bodyToFlux(clazz).collectList().block(); }
问题根因
你直接把响应体解析成Flux<T>,但实际JSON的根结构是包含_embedded的嵌套对象,目标service列表藏在_embedded下面。Jackson没法把外层的嵌套结构直接映射到T类型的对象,字段匹配不上,自然就返回null了。
解决办法
方法一:用包装类匹配JSON结构
先写一个对应JSON根结构的包装类,通过它把嵌套的列表取出来:
// 通用的HAL响应包装类 public class EmbeddedResponse<T> { @JsonProperty("_embedded") private EmbeddedContent<T> embedded; public EmbeddedContent<T> getEmbedded() { return embedded; } public void setEmbedded(EmbeddedContent<T> embedded) { this.embedded = embedded; } public static class EmbeddedContent<T> { private List<T> service; public List<T> getService() { return service; } public void setService(List<T> service) { this.service = service; } } }
然后修改WebClient的调用代码,先解析成包装类,再提取列表:
private <T> List<T> getMultipleResources(Class<T> clazz, URI uri) { ParameterizedTypeReference<EmbeddedResponse<T>> typeRef = new ParameterizedTypeReference<>() {}; EmbeddedResponse<T> response = webClient.get().uri(uri) .retrieve() .bodyToMono(typeRef) .block(); return response != null ? response.getEmbedded().getService() : Collections.emptyList(); }
方法二:用JsonPath直接定位提取
如果不想写包装类,可以用JsonPath直接定位到_embedded.service节点,一步提取列表:
private <T> List<T> getMultipleResources(Class<T> clazz, URI uri) { return webClient.get().uri(uri) .retrieve() .bodyToMono(String.class) .map(body -> JsonPath.read(body, "$._embedded.service", new TypeReference<List<T>>() {})) .block(); }
注意:用这个方法需要先引入JsonPath依赖,比如com.jayway.jsonpath:json-path。
方法三:自定义解码器(适合频繁处理这类响应)
如果项目里经常要解析这种HAL格式的响应,可以自定义一个Jackson解码器,自动处理_embedded的嵌套:
// 自定义HAL列表解码器 public class HalListDecoder<T> extends AbstractDecoder<T> { private final ObjectMapper objectMapper; public HalListDecoder(ObjectMapper objectMapper) { super(MediaType.APPLICATION_JSON); this.objectMapper = objectMapper; } @Override public T decode(DataBuffer dataBuffer, ResolvableType resolvableType, MimeType mimeType, Map<String, Object> hints) throws DecodingException { try { JsonNode root = objectMapper.readTree(dataBuffer.asInputStream()); JsonNode embeddedNode = root.get("_embedded"); if (embeddedNode != null && embeddedNode.isObject()) { // 取_embedded下第一个数组类型的属性(这里默认是service) for (JsonNode node : embeddedNode) { if (node.isArray()) { return objectMapper.treeToValue(node, resolvableType.getType()); } } } return null; } catch (IOException e) { throw new DecodingException("解析HAL响应失败", e); } } }
注册这个解码器到WebClient:
WebClient webClient = WebClient.builder() .codecs(configurer -> configurer.customCodecs().register(new HalListDecoder<>(new ObjectMapper()))) .build();
之后就可以复用原来的bodyToFlux方法,不用改太多代码。
内容的提问来源于stack exchange,提问作者Rajesh Kumar
相关产品推荐
相关产品推荐

