在日常的后端开发工作中,我们经常需要和 JSON 数据打交道,尤其是要从层级复杂的 JSON 结构里精准提取特定字段。
传统的处理方式,比如借助 Gson、Jackson 这类主流 JSON 解析库的 API,虽然功能完备,但面对复杂路径的字段提取时,写出来的代码往往又长又乱,后期维护起来也特别费劲。
今天就给大家分享一个更优雅的解决方案 —— JSONPath,它就好比 JSON 领域的 XPath,能让我们用简洁的路径表达式,轻松定位和提取 JSON 里的数据。
JSONPath 是一种专门用于从 JSON 文档中提取指定数据的查询语言。
它的语法简洁又直观,和 JavaScript 访问对象属性的方式很相似,能快速定位 JSON 结构里的任意数据节点。
核心语法说明:
想要在 Spring Boot 项目中使用 JSONPath,首先需要在 pom.xml 中引入对应的依赖:
|
1 2 3 4 5 6 |
<!-- JSONPath 核心依赖 --> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>2.9.0</version> </dependency> |
先准备一个示例 JSON 数据,方便后续演示:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
{ "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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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 接口中实战使用:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
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 配置(适配不同业务场景):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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 查询性能(缓存与预编译):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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 并提取数据:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 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 或类似功能实现,下面给大家逐一介绍。
FastJSON 作为国内常用的 JSON 解析库,内置了完善的 JSONPath 功能,使用起来非常便捷。
首先引入 FastJSON 依赖:
|
1 2 3 4 5 |
<dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.53</version> </dependency> |
基础使用示例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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 提供了多种查询方式,适配不同的业务场景:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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 数据:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
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 还支持常用的数组操作:
|
1 2 3 4 5 6 7 8 9 10 11 |
// 获取数组长度 // 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()"); |
Jackson 是 Spring Boot 默认集成的 JSON 解析库,它原生支持 JsonPointer(遵循 RFC 6901 标准),但这并不是完整的 JSONPath 实现。
如果需要使用完整的 JSONPath 功能,可参考以下两种方式:
先引入 Jackson 核心依赖:
|
1 2 3 4 5 |
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.18.2</version> </dependency> |
使用示例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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 局限性:
依赖和之前一致,只需新增 Jackson 适配配置:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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"); } } |
Gson 是 Google 推出的 JSON 解析库,它本身没有提供 JSONPath 相关功能,这也是 Gson 相比其他库的一个短板。
如果项目中已经使用了 Gson,建议搭配 Jayway JsonPath 来弥补这个不足。
首先引入依赖:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
<!-- 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> |
使用示例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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); } } |
| 特性 | FastJSON | Jackson + JsonPointer | Jayway JsonPath |
|---|---|---|---|
| JSONPath 支持 | 原生完整支持 | 仅JsonPointer(基础) | 完整支持 |
| 过滤表达式 | 支持 | 不支持 | 支持 |
| 通配符 | 支持 | 不支持 | 支持 |
| 性能 | 优秀 | 优秀 | 良好 |
| 生态稳定性 | 曾出现安全漏洞 | 最稳定 | 社区活跃 |
| Spring Boot 集成 | 需手动配置 | 默认集成 | 需添加依赖 |
JSONPath 是处理 JSON 数据的高效工具,通过简洁的路径表达式,能轻松实现复杂字段提取、条件过滤和动态查询。
在 Spring Boot 项目中集成 JSONPath,能大幅简化 JSON 处理代码,提升代码的可读性和可维护性,是处理复杂 JSON 结构、对接第三方 API 数据的优质技术方案。