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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:10:20