在日常的后端开发中,处理JSON数据是一项高频操作。当我们需要从一个嵌套较深或结构复杂的JSON文档中,精准地提取出某个特定字段时,如果只使用传统的JSON库(如Gson、Jackson)的API,代码往往会显得冗长且不易维护。
有没有更优雅、更直观的方式呢?今天我们就来聊聊 JSONPath。你可以把它理解为JSON世界的XPath,通过书写简洁的路径表达式,就能轻松定位和提取你想要的任何数据。
什么是JSONPath?
JSONPath是一种专门用于从JSON文档中查询和提取特定数据的微型语言。它的语法设计得非常直观,类似于我们访问JavaScript对象属性的方式,学习成本很低。
常用JSONPath语法速查
| 表达式 |
说明 |
$ |
根节点 |
@ |
当前节点(常用于过滤表达式) |
. 或 [] |
子节点操作符 |
.. |
递归下降,匹配任意深度的节点 |
* |
通配符,匹配所有成员或数组元素 |
[] |
下标运算符,用于访问数组元素 |
[start:end] |
数组切片 |
[?()] |
过滤表达式,用于条件筛选 |
在Spring Boot中集成JSONPath
1. 添加依赖
首先,在你的 pom.xml 文件中引入 Jayway JsonPath 的依赖。
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.9.0</version>
</dependency>
2. 基础使用示例
我们先定义一个经典的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.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的REST API中的应用
在实际的Web开发中,JSONPath 能帮助我们快速处理来自请求体或外部API的复杂JSON响应。
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. 高级用法与优化
自定义配置
你可以通过Configuration对象来定制JsonPath的行为,比如抑制异常、启用缓存等,以适应不同的场景需求。
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)
// 路径指向的叶子节点不存在时返回null
.options(Option.DEFAULT_PATH_LEAF_TO_NULL)
// 总是返回列表(即使是单值)
.options(Option.ALWAYS_RETURN_LIST)
// 启用解析缓存,提升重复解析性能
.options(Option.CACHE)
.build();
}
}
预编译与缓存
对于需要反复执行的JSONPath表达式,预编译可以显著提升性能。
@Service
public class JsonPathCacheService {
// 预编译一个常用的路径,提升性能
private final JsonPath compiledPath = JsonPath.compile("$.store.book");
public List<Map> readOptimized(String json) {
return compiledPath.read(json);
}
}
过滤表达式详解
过滤表达式 [?()] 是JSONPath最强大的功能之一,支持各种逻辑判断。
// 价格大于10的书籍
$.store.book[?(@.price > 10)]
// category 为 fiction 的书籍
$.store.book[?(@.category == 'fiction')]
// 包含 isbn 字段的书籍
$.store.book[?(@.isbn)]
// 使用正则表达式匹配作者名
$.store.book[?(@.author =~ /.*Melville.*/)]
// 多条件组合:价格低于10且分类为小说的书籍
$.store.book[?(@.price < 10 && @.category == 'fiction')]
除了 Jayway JsonPath,其他主流的JSON处理库也提供了类似的功能,下面我们来对比一下。
FastJSON - 内置JSONPath支持
FastJSON在其最新版本中直接内置了JSONPath功能,使用起来非常方便。
添加依赖
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
</dependency>
使用示例
FastJSON的JSONPath用法多样,并且支持数据的修改操作,这是一个非常实用的特性。
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);
// 方式一:使用静态方法eval(最常用)
List<String> authors = (List<String>) JSONPath.eval(object, "$.store.book.author");
// 方式二:预编译路径,性能更优
JSONPath path = JSONPath.of("$.store.book.author");
List<String> authors2 = (List<String>) path.extract(object);
// 修改数据:将第一本书的价格改为99.99
JSONPath.set(object, "$.store.book[0].price", 99.99);
// 判断路径是否存在
boolean hasIsbn = JSONPath.contains(object, "$.store.book[2].isbn");
}
}
Jackson - JsonPointer与扩展
Jackson作为SpringBoot默认的JSON库,原生支持的是 JsonPointer (RFC 6901) 标准,其功能是JSONPath的一个子集。
使用JsonPointer
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
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");
String author = root.at(ptr).asText();
// 链式写法
String title = root.at("/store/book/1/title").asText();
}
}
JsonPointer的限制:语法简单,不支持通配符 *、过滤表达式 [?()] 和数组切片,无法一次性获取多个值。
为Jackson集成完整JSONPath
如果你需要在Jackson生态中使用完整的JSONPath功能,可以集成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;
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来处理复杂的查询需求,两者可以很好地协同工作。
方案对比与选型建议
| 特性 |
FastJSON |
Jackson + JsonPointer |
Jayway JsonPath |
| JSONPath 支持 |
原生支持,功能完整 |
仅支持JsonPointer(功能子集) |
完整支持 |
| 过滤表达式 |
支持 |
不支持 |
支持 |
| 通配符 |
支持 |
不支持 |
支持 |
| 修改操作 |
支持(强大特性) |
支持(需操作JsonNode) |
仅读取 |
| 性能 |
优秀 |
优秀 |
良好 |
| Spring Boot 集成 |
需手动添加依赖 |
默认集成 |
需手动添加依赖 |
| 生态与安全 |
曾有安全漏洞历史,需关注版本 |
最稳定,社区信任度高 |
社区活跃 |
选型建议:
- 已有FastJSON项目:直接使用其内置的JSONPath,特别是需要修改JSON数据时。
- 使用Jackson的项目:简单路径定位用JsonPointer;需要复杂查询、过滤时,引入Jayway JsonPath扩展。
- 使用Gson的项目:搭配Jayway JsonPath使用。
- 新项目/Spring Boot项目:推荐使用 Jackson作为主力库,在需要复杂查询时引入 Jayway JsonPath 的组合,在稳定性、性能和功能上取得平衡。
总结
JSONPath通过其声明式的路径表达式,极大地简化了从复杂JSON结构中抽取、过滤和查询数据的过程。在Spring Boot项目中,无论是处理前端请求、解析第三方API响应,还是进行内部数据转换,合理利用JSONPath都能让代码更简洁、意图更清晰。
掌握JSONPath及其在不同JSON库中的使用方式,能让你在处理JSON数据时更加游刃有余。希望本文的梳理和对比,能帮助你在实际项目中做出更合适的技术选型。
如果你想了解更多关于Java后端开发、系统架构设计的实战技巧,欢迎持续关注云栈社区,与众多开发者一起交流成长。