Spring Boot 中使用 JSONPath 高效处理 JSON 数据

前言

在日常开发中,我们经常需要处理 JSON 数据,特别是从复杂的 JSON 结构中提取特定字段。传统的处理方式如 Gson、Jackson 的 API 虽然功能强大,但在处理复杂路径提取时代码往往显得冗长且不易维护。

今天给大家介绍一个更优雅的解决方案 ------ JSONPath,它就像 JSON 界的 XPath,让我们可以用简洁的路径表达式来定位和提取 JSON 数据。

什么是 JSONPath?

JSONPath 是一种用于从 JSON 文档中提取特定数据的查询语言。它的语法简洁直观,类似于 JavaScript 对象属性的访问方式。

常用 JSONPath 语法

表达式 说明
$ 根节点
@ 当前节点
.[] 子节点操作符
.. 递归下降(任意深度)
* 通配符,匹配所有成员/元素
[] 下标运算符
[start:end] 数组切片
[?()] 过滤表达式

Spring Boot 集成 JSONPath

1. 添加依赖

pom.xml 中添加 JSONPath 依赖:

xml 复制代码
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>2.9.0</version>
</dependency>

2. 基础使用示例

首先准备一个 JSON 示例:

json 复制代码
{
  "store": {
    "book": [
      {
        "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      {
        "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      {
        "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}
读取数据
java 复制代码
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.PathNotFoundException;

public class JsonPathExample {

    private String json = "..."; // 上述 JSON 字符串

    @Test
    public void testReadJson() {
        // 获取所有书籍的作者
        List<String> authors = JsonPath.parse(json)
            .read("$.store.book[*].author");

        // 获取第一本书的价格
        Double price = JsonPath.parse(json)
            .read("$.store.book[0].price");

        // 获取所有价格低于10元的书籍
        List<Map> cheapBooks = JsonPath.parse(json)
            .read("$.store.book[?(@.price < 10)]");

        // 获取最后一本书
        Map lastBook = JsonPath.parse(json)
            .read("$.store.book[-1]");
    }
}
在 Spring Boot 中的实际应用
java 复制代码
import org.springframework.web.bind.annotation.*;
import com.jayway.jsonpath.JsonPath;

@RestController
@RequestMapping("/api")
public class BookController {

    @PostMapping("/extract")
    public ResponseEntity<?> extractData(@RequestBody String jsonString) {
        try {
            // 提取所有书籍标题
            List<String> titles = JsonPath.parse(jsonString)
                .read("$.store.book[*].title");

            // 提取价格区间内的书籍
            List<Map> books = JsonPath.parse(jsonString)
                .read("$.store.book[?(@.price >= 8 && @.price <= 12)]");

            return ResponseEntity.ok(Map.of(
                "titles", titles,
                "filteredBooks", books
            ));
        } catch (PathNotFoundException e) {
            return ResponseEntity.badRequest()
                .body("JSON路径不存在: " + e.getMessage());
        }
    }

    @GetMapping("/authors")
    public ResponseEntity<?> getAuthors(@RequestParam String jsonData) {
        List<String> authors = JsonPath.parse(jsonData)
            .read("$.store.book[*].author");
        return ResponseEntity.ok(authors);
    }
}

3. 高级用法

自定义配置
java 复制代码
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Option;

@Configuration
public class JsonPathConfig {

    public Configuration jsonPathConfiguration() {
        return Configuration.builder()
            // 抑制异常,返回 null
            .options(Option.SUPPRESS_EXCEPTIONS)
            // 默认值为空集合
            .options(Option.DEFAULT_PATH_LEAF_TO_NULL)
            // 总是返回列表
            .options(Option.ALWAYS_RETURN_LIST)
            // 缓存
            .options(Option.CACHE)
            .build();
    }
}
缓存解析结果
java 复制代码
@Service
public class JsonPathCacheService {

    private final Map<String, Object> cache = new ConcurrentHashMap<>();

    public Object readWithCache(String json, String path) {
        return JsonPath.using(Configuration.defaultConfiguration())
            .parse(json)
            .read(path);
    }

    // 预编译路径,提升性能
    private final JsonPath compiledPath = JsonPath.compile("$.store.book[*]");

    public List<Map> readOptimized(String json) {
        return compiledPath.read(json);
    }
}
与 REST 调用结合
java 复制代码
@Service
public class ExternalApiService {

    private final RestTemplate restTemplate;

    public List<String> extractFromExternalApi(String url, String jsonPath) {
        String response = restTemplate.getForObject(url, String.class);
        return JsonPath.parse(response).read(jsonPath);
    }
}
过滤表达式详解
java 复制代码
// 价格大于10的书籍
$.store.book[?(@.price > 10)]

// category 为 fiction 的书籍
$.store.book[?(@.category == 'fiction')]

// 包含 isbn 字段的书籍
$.store.book[?(@.isbn)]

// 正则匹配
$.store.book[?(@.author =~ /.*Melville.*/)]

// 多条件组合
$.store.book[?(@.price < 10 && @.category == 'fiction')]

除了 Jayway JsonPath,常见的 JSON 处理库也有各自的 JSONPath 或类似功能实现。

FastJSON - 内置 JSONPath

FastJSON内置了 JSONPath 支持,使用起来非常简洁。

添加依赖

xml 复制代码
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.53</version>
</dependency>

使用示例

java 复制代码
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONObject;

public class FastJsonPathExample {

    private String json = "..."; // 同上 JSON 示例

    @Test
    public void testFastJsonPath() {
        JSONObject object = JSON.parseObject(json);

        // 获取所有书籍作者
        List<String> authors = (List<String>) JSONPath.eval(object, "$.store.book[*].author");

        // 获取第一本书价格
        Double price = (Double) JSONPath.eval(object, "$.store.book[0].price");

        // 过滤价格小于10的书籍
        List books = (List) JSONPath.eval(object, "$.store.book[?(@.price < 10)]");

        // size 方法
        Integer size = (Integer) JSONPath.eval(object, "$.store.book.size()");

        // 获取所有包含 isbn 的书籍
        List booksWithIsbn = (List) JSONPath.eval(object, "$.store.book[?(@.isbn)]");
    }
}

FastJSON JSONPath 多种查询方式

FastJSON 提供了多种查询方式,适应不同场景:

java 复制代码
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONObject;

public class FastJsonPathQueryExample {

    private JSONObject object = JSON.parseObject(json);

    @Test
    public void testDifferentQueryMethods() {

        // ========== 方式一:JSONPath.eval(静态方法,最常用)==========
        List authors1 = (List) JSONPath.eval(object, "$.store.book[*].author");


        // ========== 方式二:JSONPath.of + extract(推荐,性能更好)==========
        // 预编译路径表达式,性能更优(适合重复使用)
        JSONPath path = JSONPath.of("$.store.book[*].author");
        List authors2 = (List) path.extract(object);


        // ========== 方式三:compile + eval(另一种编译方式)==========
        JSONPath compiledPath = JSONPath.compile("$.store.book[*].author");
        List authors3 = (List) compiledPath.eval(object);


        // ========== 方式四:路径对象直接调用 set(修改操作)==========
        JSONPath pricePath = JSONPath.of("$.store.book[0].price");
        pricePath.set(object, 88.88);


        // ========== 方式五:contains(判断是否包含路径)==========
        boolean hasBook = JSONPath.contains(object, "$.store.book");
        boolean hasIsbn = JSONPath.contains(object, "$.store.book[2].isbn");


        // ========== 方式六:size(获取数组大小)==========
        Integer arraySize = (Integer) JSONPath.eval(object, "$.store.book.size()");
        // 或者使用编译后的路径
        JSONPath sizePath = JSONPath.of("$.store.book.size()");
        Integer size = (Integer) sizePath.eval(object);
    }
}

FastJSON JSONPath 修改操作

FastJSON 的 JSONPath 不仅可以读取数据,还支持修改数据,这是它的一个强大特性。

java 复制代码
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONObject;

public class FastJsonPathModifyExample {

    @Test
    public void testJsonPathSet() {
        JSONObject object = JSON.parseObject(json);

        // 修改第一本书的价格
        JSONPath.set(object, "$.store.book[0].price", 99.99);

        // 修改自行车的颜色
        JSONPath.set(object, "$.store.bicycle.color", "blue");

        // 批量修改所有书籍价格
        JSONPath.set(object, "$.store.book[*].price", 15.88);

        // 修改包含 isbn 的书籍的 category
        JSONPath.set(object, "$.store.book[?(@.isbn)].category", "classic");

        // 添加新字段
        JSONPath.set(object, "$.store.book[0].publisher", "Tech Press");

        // 数组末尾添加元素(通过路径获取数组后操作)
        JSONArray bookArray = (JSONArray) JSONPath.eval(object, "$.store.book");
        bookArray.add(JSON.parseObject("{\"title\":\"New Book\",\"price\":9.99}"));

        // 删除字段
        JSONPath.remove(object, "$.store.bicycle");

        System.out.println(JSON.toJSONString(object));
    }
}

FastJSON JSONPath 其他操作

java 复制代码
// 获取集合大小
Integer size = (Integer) JSONPath.eval(object, "$.store.book.size()");

// 获取集合第一个
Object first = JSONPath.eval(object, "$.store.book.first()");

// 获取集合最后一个
Object last = JSONPath.eval(object, "$.store.book.last()");

// 获取属性所有值
Collection values = (Collection) JSONPath.eval(object, "$.store.book.values()");

Jackson - JsonPointer / Jackson JsonPath

Jackson 原生支持 JsonPointer (RFC 6901),但不是完整的 JSONPath 实现。若要使用 JSONPath 功能,可以通过以下两种方式:

方式一:使用 JsonPointer(原生支持)

xml 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.18.2</version>
</dependency>
java 复制代码
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonPointer;

public class JacksonJsonPointerExample {

    private String json = "...";
    private ObjectMapper mapper = new ObjectMapper();

    @Test
    public void testJsonPointer() throws Exception {
        JsonNode root = mapper.readTree(json);

        // 使用 JsonPointer 定位节点
        JsonPointer ptr = JsonPointer.compile("/store/book/0/author");
        JsonNode authorNode = root.at(ptr);
        String author = authorNode.asText();

        // 链式写法
        String title = root.at("/store/book/1/title").asText();
        Double price = root.at("/store/bicycle/price").asDouble();
    }
}

JsonPointer 限制

  • 语法较简单,不支持通配符、过滤表达式
  • 无法一次获取多个值
  • 不支持数组切片

方式二:使用 Jackson-JsonPath(第三方扩展)

xml 复制代码
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>2.9.0</version>
</dependency>
java 复制代码
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;

public class JacksonJsonPathExample {

    // 配置使用 Jackson
    private Configuration configuration = Configuration.builder()
        .jsonProvider(new JacksonJsonNodeJsonProvider())
        .mappingProvider(new JacksonMappingProvider())
        .build();

    @Test
    public void testJacksonJsonPath() {
        List<String> authors = JsonPath.using(configuration)
            .parse(json)
            .read("$.store.book[*].author");
    }
}

Gson - 无原生 JSONPath

Gson 本身不提供 JSONPath 支持,这是 Gson 的一个局限。建议搭配 Jayway JsonPath 使用。

xml 复制代码
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.11.0</version>
</dependency>

<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>2.9.0</version>
</dependency>
java 复制代码
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.jayway.jsonpath.JsonPath;

public class GsonJsonPathExample {

    private Gson gson = new Gson();
    private String json = "...";

    @Test
    public void testGsonWithJsonPath() {
        // 使用 JsonPath 提取数据
        List<String> authors = JsonPath.parse(json)
            .read("$.store.book[*].author");

        // 将结果转回 Gson 对象
        JsonElement element = gson.toJsonTree(authors);
    }
}

三种方案对比

特性 FastJSON Jackson + JsonPointer Jayway JsonPath
JSONPath 支持 原生支持 仅 JsonPointer 完整支持
过滤表达式 支持 不支持 支持
通配符 支持 不支持 支持
性能 优秀 优秀 良好
生态稳定性 曾有安全漏洞 最稳定 社区活跃
Spring Boot 集成 需手动配置 默认集成 需添加依赖

选型建议

  • 已有 FastJSON 项目:直接使用 FastJSON 的 JSONPath
  • 使用 Jackson 的项目:简单场景用 JsonPointer,复杂场景引入 Jayway JsonPath
  • 使用 Gson 的项目:建议搭配 Jayway JsonPath 使用
  • 新项目:推荐 Jackson + Jayway JsonPath 组合

总结

JSONPath 是处理 JSON 数据的利器,通过简洁的路径表达式实现复杂字段提取、条件过滤和动态查询。在 Spring Boot 中集成 JSONPath 可大幅简化代码、提升可读性,是处理复杂 JSON 结构和第三方 API 数据的一种可选技术方案。

相关推荐
KYGALYX25 分钟前
服务异步通信
开发语言·后端·微服务·ruby
掘了31 分钟前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法1 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment1 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
Cobyte2 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
程序员侠客行3 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple3 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端
PP东3 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
invicinble4 小时前
springboot的核心实现机制原理
java·spring boot·后端
全栈老石4 小时前
Python 异步生存手册:给被 JS async/await 宠坏的全栈工程师
后端·python