在日常的后端开发工作中,我们经常需要和 JSON 数据打交道,尤其是要从层级复杂的 JSON 结构里精准提取特定字段。
传统的处理方式,比如借助 Gson、Jackson 这类主流 JSON 解析库的 API,虽然功能完备,但面对复杂路径的字段提取时,写出来的代码往往又长又乱,后期维护起来也特别费劲。
今天就给大家分享一个更优雅的解决方案 ------ JSONPath,它就好比 JSON 领域的 XPath,能让我们用简洁的路径表达式,轻松定位和提取 JSON 里的数据。
02
什么是 JSONPath?
JSONPath 是一种专门用于从 JSON 文档中提取指定数据的查询语言。
它的语法简洁又直观,和 JavaScript 访问对象属性的方式很相似,能快速定位 JSON 结构里的任意数据节点。
核心语法说明:
-
•
$:代表 JSON 结构的根节点 -
•
@:代表当前节点 -
•
.:子节点操作符 -
•
..:递归下降,可匹配任意深度的节点 -
•
*:通配符,匹配所有成员/数组元素 -
•
[]:下标运算符,用于数组索引 -
•
[start:end]:数组切片,截取指定范围的数组元素 -
•
[?()]:过滤表达式,按条件筛选数据
03
Spring Boot 集成 JSONPath
想要在 Spring Boot 项目中使用 JSONPath,首先需要在 pom.xml 中引入对应的依赖:
<!-- JSONPath 核心依赖 -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.9.0</version>
</dependency>
先准备一个示例 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
}
}
}
基础使用示例(添加详细注解):
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import java.util.List;
import java.util.Map;
public class JsonPathExample {
// 示例JSON字符串(实际使用时替换为真实数据)
private String 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}}}";
public void testReadJson() {
// 1. 提取所有书籍的作者
List<String> authors = JsonPath.parse(json)
.read("$.store.book[*].author");
// 2. 提取第一本书的价格
Double price = JsonPath.parse(json)
.read("$.store.book[0].price");
// 3. 过滤价格低于10元的书籍
List<Map<String, Object>> cheapBooks = JsonPath.parse(json)
.read("$.store.book[?(@.price < 10)]");
// 4. 提取最后一本书的完整信息
Map<String, Object> lastBook = JsonPath.parse(json)
.read("$.store.book[-1]");
}
}
在 Spring Boot 接口中实战使用:
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class BookController {
/**
* 提取JSON中的书籍标题和指定价格区间的书籍
* @param jsonString 传入的JSON字符串
* @return 包含标题和过滤后书籍的响应
*/
@PostMapping("/extractBookData")
public ResponseEntity<?> extractData(@RequestBody String jsonString) {
try {
// 提取所有书籍的标题
List<String> titles = JsonPath.parse(jsonString)
.read("$.store.book[*].title");
// 过滤价格大于等于8且小于等于15的书籍(补全原代码语法错误)
List<Map<String, Object>> books = JsonPath.parse(jsonString)
.read("$.store.book[?(@.price >= 8 && @.price <= 15)]");
return ResponseEntity.ok(Map.of(
"titles", titles,
"filteredBooks", books
));
} catch (PathNotFoundException e) {
// 路径不存在时返回错误提示
return ResponseEntity.badRequest()
.body("JSON路径不存在: " + e.getMessage());
}
}
/**
* 提取所有书籍的作者
* @param jsonData 传入的JSON字符串
* @return 作者列表
*/
@PostMapping("/getBookAuthors")
public ResponseEntity<?> getAuthors(@RequestBody String jsonData) {
List<String> authors = JsonPath.parse(jsonData)
.read("$.store.book[*].author");
return ResponseEntity.ok(authors);
}
}
自定义 JSONPath 配置(适配不同业务场景):
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Option;
public class JsonPathConfig {
/**
* 构建自定义的JSONPath配置
* @return 配置实例
*/
public Configuration jsonPathConfiguration() {
return Configuration.builder()
// 抑制异常(路径不存在时不抛异常)
.options(Option.SUPPRESS_EXCEPTIONS)
// 路径叶子节点不存在时返回null
.options(Option.DEFAULT_PATH_LEAF_TO_NULL)
// 始终返回List类型(即使结果只有一个元素)
.options(Option.ALWAYS_RETURN_LIST)
// 开启缓存(提升重复路径查询性能)
.options(Option.CACHE)
.build();
}
}
优化 JSONPath 查询性能(缓存与预编译):
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class JsonPathCacheService {
// 缓存容器,存储已解析的JSON数据(线程安全)
private final Map<String, Object> cache = new ConcurrentHashMap<>();
/**
* 带缓存的JSONPath读取(基础版)
* @param json 原始JSON字符串
* @param path JSONPath表达式
* @return 提取的数据
*/
public Object readWithCache(String json, String path) {
return JsonPath.using(Configuration.defaultConfiguration())
.parse(json)
.read(path);
}
// 预编译常用的JSONPath表达式(避免重复编译,提升性能)
private final JsonPath compiledPath = JsonPath.compile("$.store.book[*]");
/**
* 使用预编译路径查询所有书籍
* @param json 原始JSON字符串
* @return 书籍列表
*/
public List<Map<String, Object>> readOptimized(String json) {
return compiledPath.read(json);
}
}
对接外部 API 并提取数据:
import com.jayway.jsonpath.JsonPath;
import org.springframework.web.client.RestTemplate;
import java.util.List;
public class ExternalApiService {
private final RestTemplate restTemplate;
// 构造器注入RestTemplate
public ExternalApiService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
/**
* 调用外部API并通过JSONPath提取数据
* @param url 外部API地址
* @param jsonPath JSONPath表达式
* @return 提取的数据列表
*/
public List<Object> extractFromExternalApi(String url, String jsonPath) {
// 调用外部API获取JSON响应
String response = restTemplate.getForObject(url, String.class);
// 解析并提取指定路径的数据
return JsonPath.parse(response).read(jsonPath);
}
}
常用过滤表达式示例:
// 1. 价格大于10的书籍
// $.store.book[?(@.price > 10)]
// 2. 分类为fiction的书籍
// $.store.book[?(@.category == 'fiction')]
// 3. 包含isbn字段的书籍
// $.store.book[?(@.isbn)]
// 4. 作者名称包含Melville的书籍(正则匹配)
// $.store.book[?(@.author =~ /.*Melville.*/)]
// 5. 价格低于10且分类为fiction的书籍
// $.store.book[?(@.price < 10 && @.category == 'fiction')]
除了 Jayway JsonPath,市面上主流的 JSON 处理库也都有各自的 JSONPath 或类似功能实现,下面给大家逐一介绍。
04
FastJSON - 内置原生 JSONPath 支持
FastJSON 作为国内常用的 JSON 解析库,内置了完善的 JSONPath 功能,使用起来非常便捷。
首先引入 FastJSON 依赖:
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
</dependency>
基础使用示例:
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONObject;
import java.util.List;
public class FastJsonPathExample {
// 示例JSON字符串
private String 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}}}";
public void testFastJsonPath() {
// 将JSON字符串解析为JSONObject
JSONObject object = JSON.parseObject(json);
// 1. 提取所有书籍的作者
List<String> authors = (List<String>) JSONPath.eval(object, "$.store.book[*].author");
// 2. 提取第一本书的价格
Double price = (Double) JSONPath.eval(object, "$.store.book[0].price");
// 3. 过滤价格低于10的书籍
List<JSONObject> books = (List<JSONObject>) JSONPath.eval(object, "$.store.book[?(@.price < 10)]");
// 4. 获取书籍数组的长度
Integer size = (Integer) JSONPath.eval(object, "$.store.book.size()");
// 5. 提取包含isbn字段的书籍
List<JSONObject> booksWithIsbn = (List<JSONObject>) JSONPath.eval(object, "$.store.book[?(@.isbn)]");
}
}
FastJSON 提供了多种查询方式,适配不同的业务场景:
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONObject;
import java.util.List;
public class FastJsonPathQueryExample {
// 解析后的JSONObject对象
private JSONObject object = JSON.parseObject("{\"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}}}");
public void testDifferentQueryMethods() {
// 方式1:直接调用eval方法(最简洁)
List<String> authors1 = (List<String>) JSONPath.eval(object, "$.store.book[*].author");
// 方式2:先创建Path对象,再调用extract方法
JSONPath path = JSONPath.of("$.store.book[*].author");
List<String> authors2 = (List<String>) path.extract(object);
// 方式3:预编译Path表达式(重复使用时推荐)
JSONPath compiledPath = JSONPath.compile("$.store.book[*].author");
List<String> authors3 = (List<String>) compiledPath.eval(object);
// 修改指定路径的值
JSONPath pricePath = JSONPath.of("$.store.book[0].price");
pricePath.set(object, 88.88);
// 判断指定路径是否存在
boolean hasBook = JSONPath.contains(object, "$.store.book");
boolean hasIsbn = JSONPath.contains(object, "$.store.book[2].isbn");
// 获取数组长度(两种方式)
Integer arraySize = (Integer) JSONPath.eval(object, "$.store.book.size()");
JSONPath sizePath = JSONPath.of("$.store.book.size()");
Integer size = (Integer) sizePath.eval(object);
}
}
FastJSON 的 JSONPath 一大亮点是支持修改 JSON 数据:
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONPath;
import com.alibaba.fastjson2.JSONObject;
public class FastJsonPathModifyExample {
public void testJsonPathSet() {
// 解析JSON字符串为可修改的JSONObject
JSONObject object = JSON.parseObject("{\"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}}}");
// 1. 修改第一本书的价格
JSONPath.set(object, "$.store.book[0].price", 99.99);
// 2. 修改自行车的颜色
JSONPath.set(object, "$.store.bicycle.color", "blue");
// 3. 批量修改所有书籍的价格
JSONPath.set(object, "$.store.book[*].price", 15.88);
// 4. 修改包含isbn字段的书籍分类
JSONPath.set(object, "$.store.book[?(@.isbn)].category", "classic");
// 5. 给第一本书新增出版社字段
JSONPath.set(object, "$.store.book[0].publisher", "Tech Press");
// 6. 向书籍数组中添加新书籍
JSONArray bookArray = (JSONArray) JSONPath.eval(object, "$.store.book");
bookArray.add(JSON.parseObject("{\"title\":\"New Book\",\"price\":9.99}"));
// 7. 删除自行车节点
JSONPath.remove(object, "$.store.bicycle");
// 打印修改后的JSON(格式化输出)
System.out.println(JSON.toJSONString(object, true));
}
}
FastJSON 还支持常用的数组操作:
// 获取数组长度
// 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<Object> values = (Collection<Object>) JSONPath.eval(object, "$.store.book.values()");
05
Jackson - JsonPointer / Jackson JsonPath
Jackson 是 Spring Boot 默认集成的 JSON 解析库,它原生支持 JsonPointer(遵循 RFC 6901 标准),但这并不是完整的 JSONPath 实现。
如果需要使用完整的 JSONPath 功能,可参考以下两种方式:
方式1:使用原生 JsonPointer(适合简单场景)
先引入 Jackson 核心依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
使用示例:
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonJsonPointerExample {
// 示例JSON字符串
private String 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}}}";
// Jackson核心解析器
private ObjectMapper mapper = new ObjectMapper();
public void testJsonPointer() throws Exception {
// 将JSON字符串解析为JsonNode(不可变节点)
JsonNode root = mapper.readTree(json);
// 1. 构建JsonPointer并提取第一本书的作者
JsonPointer ptr = JsonPointer.compile("/store/book/0/author");
JsonNode authorNode = root.at(ptr);
String author = authorNode.asText();
// 2. 直接通过路径字符串提取第二本书的标题
String title = root.at("/store/book/1/title").asText();
// 3. 提取自行车的价格
Double price = root.at("/store/bicycle/price").asDouble();
}
}
JsonPointer 局限性:
-
• 语法简单,仅支持基础的路径访问,不支持通配符、过滤表达式;
-
• 无法一次性获取多个节点的值;
-
• 不支持数组切片操作;
方式2:结合 Jayway JsonPath(适合复杂场景)
依赖和之前一致,只需新增 Jackson 适配配置:
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import java.util.List;
public class JacksonJsonPathExample {
// 示例JSON字符串
private String 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}}}";
// 构建适配Jackson的JSONPath配置
private Configuration configuration = Configuration.builder()
.jsonProvider(new JacksonJsonNodeJsonProvider())
.mappingProvider(new JacksonMappingProvider())
.build();
public void testJacksonJsonPath() {
// 提取所有书籍的作者(使用Jackson作为底层解析器)
List<String> authors = JsonPath.using(configuration)
.parse(json)
.read("$.store.book[*].author");
}
}
06
Gson - 无原生 JSONPath 支持
Gson 是 Google 推出的 JSON 解析库,它本身没有提供 JSONPath 相关功能,这也是 Gson 相比其他库的一个短板。
如果项目中已经使用了 Gson,建议搭配 Jayway JsonPath 来弥补这个不足。
首先引入依赖:
<!-- Gson核心依赖 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>
<!-- JSONPath依赖 -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.9.0</version>
</dependency>
使用示例:
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.jayway.jsonpath.JsonPath;
import java.util.List;
public class GsonJsonPathExample {
// Gson解析器实例
private Gson gson = new Gson();
// 示例JSON字符串
private String 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}}}";
public void testGsonWithJsonPath() {
// 1. 使用JSONPath提取所有书籍的作者
List<String> authors = JsonPath.parse(json)
.read("$.store.book[*].author");
// 2. 将提取的结果转换为Gson的JsonElement(适配Gson生态)
JsonElement element = gson.toJsonTree(authors);
}
}
07
三种方案对比
| 特性 | FastJSON | Jackson + JsonPointer | Jayway JsonPath |
|---|---|---|---|
| JSONPath 支持 | 原生完整支持 | 仅JsonPointer(基础) | 完整支持 |
| 过滤表达式 | 支持 | 不支持 | 支持 |
| 通配符 | 支持 | 不支持 | 支持 |
| 性能 | 优秀 | 优秀 | 良好 |
| 生态稳定性 | 曾出现安全漏洞 | 最稳定 | 社区活跃 |
| Spring Boot 集成 | 需手动配置 | 默认集成 | 需添加依赖 |
08
选型建议
-
• 已有 FastJSON 项目:直接使用 FastJSON 内置的 JSONPath 功能,无需额外引入依赖;
-
•
-
• 使用 Jackson 的项目:简单的字段提取场景用原生 JsonPointer 即可,复杂的过滤、批量提取场景建议引入 Jayway JsonPath;
-
•
-
• 使用 Gson 的项目:搭配 Jayway JsonPath 使用,弥补 Gson 无 JSONPath 的短板;
-
•
-
• 新项目:推荐使用 Jackson + Jayway JsonPath 组合,兼顾生态稳定性和功能完整性;
JSONPath 是处理 JSON 数据的高效工具,通过简洁的路径表达式,能轻松实现复杂字段提取、条件过滤和动态查询。
在 Spring Boot 项目中集成 JSONPath,能大幅简化 JSON 处理代码,提升代码的可读性和可维护性,是处理复杂 JSON 结构、对接第三方 API 数据的优质技术方案。