前言
在日常开发中,我们经常需要处理 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 数据的一种可选技术方案。