使用背景
在爬取第三方接口获取返回值时,返回Json数据的层级很多,只能一层一层转换或是创建接收Bean,取值不方便;JsonPath可以高效地解决这个问题。
一、简介
JsonPath表达式是用类似于XPath在XML文档中的定位,来检索设置Json。
JsonPath中的“根成员对象”总是被引用为$,不管它是对象还是数组。
表达式可以接受“点表达式”:
$.store.book[0].title
或者“括号表达式”:
$['store']['book'][0]['title']
引入maven依赖即可使用
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
二、操作符
操作符 | 描述 |
---|---|
$ | 查询的根元素,启动所有的路径表达式 |
* | 通配符 |
… | 递归搜索 |
.<name> | 子节点 |
@ | 过滤器处理的当前节点 |
[’<name>’ (, ‘<name>’)] | 一个或多个子节点 |
[<number> (, <number>)] | 表示一个或多个数组下标 |
[?(<expression>)] | 过滤器表达式,表达式结果必须是boolean |
[start:end] | 数组片段,区间为[start,end),不包含end |
三、函数
函数 | 描述 | 输出结果 |
---|---|---|
min() | 获取数值类型数组的最小值 | Double |
max() | 获取数值类型数组的最大值 | Double |
avg() | 获取数值类型数组的平均值 | Double |
stddev() | 获取数值类型数组的标准差 | Double |
length() | 获取数值类型数组的长度 | Integer |
sum() | 获取数值类型数组的和值 | Double |
四、过滤操作符
操作符 | 描述 |
---|---|
== | 等于,但数字1不等于字符1 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
=~ | 判断是否符合正则表达式,例如 [?(@.name =~ /foo.*?/i)] |
in | 左边存在于右边,例如 [?(@.size in [‘S’, ‘M’])] |
nin | 左边不存在于右边 |
size | 数组或字符的长度 |
empty | 数组或字符为空 |
五、示例
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
},
{
"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
}
},
"expensive": 10
}
路径 | 结果 |
---|---|
$.store.book[*].author | 获取json中store下book下的所有author值 |
$…author | 获取所有json中所有author的值 |
$.store.* | 获取所有的东西,书籍和自行车 |
$.store…price | 获取json中store下所有price的值 |
$…book[2] | 获取json中book数组的第3个值 |
$…book[-2:] | 倒数的第二本书 |
$…book[0,1] | 前两本书 |
$…book[:2] | 从索引0(包括)到索引2(排除)的所有图书 |
$…book[1:2] | 从索引1(包括)到索引2(排除)的所有图书 |
$…book[-2:] | 获取json中book数组的最后两个值 |
$…book[2:] | 获取json中book数组的第3个到最后一个的区间值 |
$…book[?(@.isbn)] | 获取json中book数组中包含isbn的所有值 |
$.store.book[?(@.price < 10)] | 获取json中book数组中price<10的所有值 |
$…book[?(@.price <= $[‘expensive’])] | 获取json中book数组中price<=expensive的所有值 |
$…book[?(@.author =~ /.*REES/i)] | 获取json中book数组中的作者以REES结尾的所有值(REES不区分大小写) |
$…* | 逐层列出json中的所有值,层级由外到内 |
$…book.length() | 获取json中book数组的长度 |
六、常见用法
- JsonPath最简单的方式是直接通过静态API读取
String json = "...";
List<String> authors = JsonPath.read(json, "$.store.book[*].author");
如果是多次读取路径,由于每次JsonPath.read()都会解析一次Json,所以要先解析Json再读取
String json = "...";
Object document = Configuration.defaultConfiguration().jsonProvider().parse(json);
String author0 = JsonPath.read(document, "$.store.book[0].author");
String author1 = JsonPath.read(document, "$.store.book[1].author");
- JsonPath还提供Fluent API用法
String json = "...";
ReadContext ctx = JsonPath.parse(json);
List<String> authorsOfBooksWithISBN = ctx.read("$.store.book[?(@.isbn)].author");
List<Map<String, Object>> expensiveBooks = JsonPath
.using(configuration)
.parse(json)
.read("$.store.book[?(@.price > 10)]", List.class);
七、返回值
- JsonPath会自动解析返回结果,转换类型
// 抛出 java.lang.ClassCastException
List<String> list = JsonPath.parse(json).read("$.store.book[0].author")
// 正常
String author = JsonPath.parse(json).read("$.store.book[0].author")
- 如果JsonPath读取中包含类似以下不确定的路径,则返回的是一个列表
- …
- ?(<expression>)
- [<number>, <number> (, <number>)]
- 默认情况下,MappingProvider SPI提供了一个简单的对象映射器。 允许指定所需的返回类型,MappingProvider将尝试执行映射
String json = "{\"date_as_long\" : 1411455611975}";
Date date = JsonPath.parse(json).read("$['date_as_long']", Date.class);
如果把JsonPath配置为JacksonMappingProvider或GsonMappingProvider,也可以将JsonPath的输出直接映射到POJO
Book book = JsonPath.parse(json).read("$.store.book[0]", Book.class);
八、断言
JsonPath有三种不同方式来创建过滤器断言
- 内联断言
内联断言是直接在路径中定义,可以使用 && 和 || 结合多个断言,也可以使用!否定断言
List<Map<String, Object>> books = JsonPath.parse(json).read("$.store.book[?(@.price < 10)]");
- 过滤断言
过滤器路径中的 ? 为占位符。 当有多个过滤器时,占位符和过滤器的数量必须匹配
import static com.jayway.jsonpath.JsonPath.parse;
import static com.jayway.jsonpath.Criteria.where;
import static com.jayway.jsonpath.Filter.filter;
...
...
Filter cheapFictionFilter = filter(
where("category").is("fiction").and("price").lte(10D)
);
List<Map<String, Object>> books =
parse(json).read("$.store.book[?]", cheapFictionFilter);
- 自定义
Predicate booksWithISBN = new Predicate() {
@Override
public boolean apply(PredicateContext ctx) {
return ctx.item(Map.class).containsKey("isbn");
}
};
List<Map<String, Object>> books = reader.read("$.store.book[?].isbn", List.class, booksWithISBN);
九、调整配置
当创建配置时,有一些选项可以进行调整
// 示例json
[
{
"name" : "john",
"gender" : "male"
},
{
"name" : "ben"
}
]
- DEFAULT_PATH_LEAF_TO_NULL
这个选项可以让JsonPath在缺少子项时返回null
// 原配置
Configuration conf = Configuration.defaultConfiguration();
// 正常
String gender0 = JsonPath.using(conf).parse(json).read("$[0]['gender']");
// 抛出 PathNotFoundException
String gender1 = JsonPath.using(conf).parse(json).read("$[1]['gender']");
// 修改后的配置
Configuration conf2 = conf.addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL);
// 正常
String gender0 = JsonPath.using(conf2).parse(json).read("$[0]['gender']");
// 正常(返回null)
String gender1 = JsonPath.using(conf2).parse(json).read("$[1]['gender']");
- ALWAYS_RETURN_LIST
这个选项会直接返回列表
Configuration conf = Configuration.addOptions(Option.ALWAYS_RETURN_LIST);
List<String> genders0 = JsonPath.using(conf).parse(json).read("$[0]['gender']");
// 返回值
["male"]
- AS_PATH_LIST
Configuration conf = Configuration.addOptions(Option.AS_PATH_LIST);
List<String> genders0 = JsonPath.using(conf).parse(json).read("$[0]['gender']");
// 返回值
["$[0]['gender']"]
- SUPPRESS_EXCEPTIONS
这个选项让表达式不会抛出异常,遵循以下规则:
- 如果选项ALWAYS_RETURN_LIST存在,将返回一个空列表
- 如果选项ALWAYS_RETURN_LIST不存在返回null
- REQUIRE_PROPERTIES
设置该选项则不允许使用通配符,比如 $[*].b 表达式,会抛出 PathNotFoundException 异常