由于准备基于 JsonPath 自定义一套规则,于是想仔细研究一下 JsonPath,于是整理了一篇通关指南。
github 地址:github.com/json-path/J…
JsonPath 使用符号
JsonPath 可以使用点符号和括号:
点符号 $.store.book[0].title
括号符号: $['store']['book'][0]['title']
素材准备(官网素材):
{
"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
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
接下来所有的测试都是用这段 JSON代码。 下面所有的测试代码都放在 gitee (gitee.com/uzongn/json…)上面,可以下载测试允许。
操作符号
$
所有路径表达式的开头;也可以理解为 json 的根元素。
// 获取所有书籍的作者
List<String> authors = JsonPath.read(jsonString, "$.store.book[*].author");
assertEquals(4, authors.size());
assertTrue(authors.contains("Nigel Rees"));
注意:所有表达式都是 $
开头。
@
表示当前元素,通常在过滤表达式中使用
// 1. 获取所有价格大于10的书籍
List<Map<String, Object>> expensiveBooks = JsonPath.read(jsonString, "$.store.book[?(@.price > 10)]");
assertEquals(2, expensiveBooks.size());
$.store.book[?(@.price > 10)]")
通过 @
表示当前节点。(类似for循环中的当前变量对象)
*
通配符,表示匹配所有元素。例如,$.store.book[*]
表示访问 store
下的所有 book
元素
..
匹配所有子元素。例如,$.store..price
表示访问所有层级的 price
属性
.
点符号子项
// 获取所有书籍的作者
List<String> authors = JsonPath.read(jsonString, "$.store.book[*].author");
assertEquals(4, authors.size());
assertTrue(authors.contains("Nigel Rees"));
?()
过滤表达式,用于根据条件过滤数组元素。例如,$.store.book[?(@.price < 10)]
表示访问价格小于 10 的所有书籍。
注意:表达式必须计算为布尔值。
// 获取所有fiction类别的书籍
List<Map<String, Object>> fictionBooks = JsonPath.read(jsonString, "$.store.book[?(@.category == 'fiction')]");
assertEquals(3, fictionBooks.size());
[]
用于访问数组的元素。例如,$.store.book[0]
表示访问 store
下的 book
数组的第一个元素。
$.store.book[0,2]: 表示第 1 本和第 3 本
$.store.book[-1]:表示最后一本
[start:end]
对数组进行切片。
// 获取前两本书
List<Map<String, Object>> firstTwoBooks = JsonPath.read(jsonString, "$.store.book[0:2]");
assertEquals(2, firstTwoBooks.size());
注意这里在数学意义上属于左闭右开 [)
。
$.store.book[0:2]: 表示前两本
$.store.book[-2:]: 表示最后两本
$.store.book[:2]:表示从0到1 (不包含索引2)
&& or || or !
谓词
// 价格范围过滤
List<Map<String, Object>> priceRange = JsonPath.read(jsonString,
"$.store.book[?(@.price >= 8 && @.price <= 12)]");
assertEquals(2, priceRange.size());
使用&&
和||
来组合多个谓词[?(@.price < 10 && @.category == 'fiction')]
。 [?(@.category == 'reference' || @.price > 10)]
用来!
否定谓词[?(!(@.price < 10 && @.category == 'fiction'))]
。
操作符
=~
正则匹配。 从书籍数组中筛选出标题包含 "of" 的书籍,并提取其标题
List<String> matchingTitles = JsonPath.read(jsonString,
"$.store.book[?(@.title =~ /.*of.*/i)].title");
assertEquals(2, matchingTitles.size());
i
是一个修饰符(或标志),表示不区分大小写(case-insensitive)
函数
distinct
// 获取不重复的分类
List<String> uniqueCategories = JsonPath.read(jsonString,
"$.store.book[*].category.distinct()");
assertTrue(uniqueCategories.size() < 4); // 因为有重复的 fiction 类别
sort
// 对价格进行排序
List<Double> sortedPrices = JsonPath.read(jsonString,
"$.store.book[*].price.sort()");
assertTrue(sortedPrices.get(0) < sortedPrices.get(sortedPrices.size() - 1));
reverse
// 反向排序
List<Double> reverseSortedPrices = JsonPath.read(jsonString,
"$.store.book[*].price.sort().reverse()");
assertTrue(reverseSortedPrices.get(0) > reverseSortedPrices.get(reverseSortedPrices.size() - 1));
toUpperCase/replace
// 转换为大写
List<String> upperTitles = JsonPath.read(jsonString,
"$.store.book[*].title.toUpperCase()");
assertTrue(upperTitles.stream().allMatch(t -> t.equals(t.toUpperCase())));
// 字符串替换
List<String> modifiedTitles = JsonPath.read(jsonString,
"$.store.book[*].title.replace(' ', '_')");
assertTrue(modifiedTitles.stream().anyMatch(t -> t.contains("_")));
round/ceil/floor
// 四舍五入
List<Double> roundedPrices = JsonPath.read(jsonString,
"$.store.book[*].price.round()");
assertTrue(roundedPrices.stream().allMatch(p -> p == Math.round(p)));
// 向上取整
List<Double> ceilingPrices = JsonPath.read(jsonString,
"$.store.book[*].price.ceil()");
assertTrue(ceilingPrices.stream().allMatch(p -> p >= 0));
// 向下取整
List<Double> floorPrices = JsonPath.read(jsonString,
"$.store.book[*].price.floor()");
assertTrue(floorPrices.stream().allMatch(p -> p >= 0));
contains/startsWith/endsWith
// 使用 contains
List<Map<String, Object>> booksWithOf = JsonPath.read(jsonString,
"$.store.book[?(@.title.contains('of'))]");
assertTrue(booksWithOf.size() > 0);
// 使用 startsWith
List<Map<String, Object>> booksStartWithThe = JsonPath.read(jsonString,
"$.store.book[?(@.title.startsWith('The'))]");
assertTrue(booksStartWithThe.size() > 0);
// 使用 endsWith
List<Map<String, Object>> booksEndWithS = JsonPath.read(jsonString,
"$.store.book[?(@.title.endsWith('s'))]");
assertTrue(booksEndWithS.size() > 0);
常见陷阱与解决方案
空值处理
# 可能导致NPE的写法
$.store.book[?(@.price > 10)]
# 安全的写法
$.store.book[?(@.price != null && @.price > 10)]
类型转换问题
# 可能的类型转换问题
$.store.book[?(@.price == "10.99")]
# 正确的写法
$.store.book[?(@.price == 10.99)]
避免递归下降
# 不推荐
$..book[*].price # 性能较差,可能遍历整个文档
# 推荐
$.store.book[*].price # 明确的路径,性能更好
数组边界检查
# 不安全
$.store.book[0].title
# 安全
$.store.book[?(@.length() > 0)][0].title
# 或使用默认值(某些实现支持)
$.store.book[0].title default 'Unknown'
其他
性能相关
JSONPath 表达式的效率取决于其复杂度和数据量。在处理大型 JSON 数据时,过于复杂的表达式会显著影响性能。例如,尽量避免使用过多的递归查询或复杂的过滤条件。
最后
对于 JsonPath 的使用,就到这里,基本可以应付80%的场景,如果需要可以查阅官网了解更多细节。github.com/json-path/J…