操做符号 | 描述 |
---|---|
$ |
要查询的根元素。它启动所有路径表达式。 |
@ |
过滤谓词正在处理的当前节点。 |
* |
通配符。可用于任何需要名称或数字的地方。 |
.. |
深度扫描。可在任何需要名称的地方使用。 |
. |
子类 |
[' |
括号中注明的一个或多个子女括号中注明的一个或多个子女 |
[ |
数组索引 |
[start:end] |
数组切分运算符 |
[?( |
过滤表达式。表达式的值必须是布尔值。 |
函数可以在路径的尾端调用–函数的输入就是路径表达式的输出。函数的输出由函数本身决定。
Functions can be invoked at the tail end of a path - the input to a function is the output of the path expression. The function output is dictated by the function itself.
方法 | 描述 | 输出类型 |
---|---|---|
min() | 提供数字数组的最小值 | Double |
max() | 提供数字数组的最大值 | Double |
avg() | 提供数字数组的平均值 | Double |
stddev() | 提供数字数组的标准偏差值 | Double |
length() | 提供数组的长度 | Integer |
sum() | 提供数字数组的和值 | Double |
keys() | 提供属性键(终端子键 ~ 的替代选择) |
Set |
concat(X) | 提供路径输出的合并版本,并添加一个新项目 | like input |
append(X) | 为 json 路径输出数组添加项目 | like input |
first() | 提供数组的第一个项目 | Depends on the array |
last() | 提供数组的最后一项 | Depends on the array |
index(X) | 提供索引数组的项: X,如果 X 为负数,则向后取值 | Depends on the array |
Filter Operators |
过滤器是用于过滤数组的逻辑表达式。一个典型的过滤器是 [?(@.age > 18)] ,其中 @ 代表正在处理的当前项目。使用逻辑运算符 && 和 || 可以创建更复杂的过滤器。字符串文字必须用单引号或双引号([?(@.color == ‘blue’)]或[?(@.color == “blue”)])括起来。
运算符号 | 描述 |
---|---|
== | 左等于右(注意 "1 "不等于 "1) |
!= | 左不等于右 |
< | 左小于右 |
<= | 左小于或等于右 |
> | 左大于右 |
>= | 左大于或等于右 |
=~ | 左侧匹配正则表达式 [?(@.name =~ /foo.*?/i)] |
in | 左边存在于右边[?(@.size in [‘S’, ‘M’])] |
nin | 左不存在于右 |
subsetof | 左是右的子集 [?(@.sizes subsetof [‘S’, ‘M’, ‘L’])] |
anyof | 左与右有交集 [?(@.sizes anyof [‘M’, ‘L’])] |
noneof | 左边与右边没有交集 [?(@.sizes noneof [‘M’, ‘L’])] |
size | 左侧(数组或字符串)的大小应与右侧一致 |
empty | left(数组或字符串)应为空 |
{
"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
}
JsonPath | Result |
---|---|
$.store.book[*].author | 所有书籍的作者 |
$…author | 所有作者 |
$.store.* | 万物,包括书籍和自行车 |
$.store…price | 所有书籍的价格 |
$…book[2] | 第三本书 |
$…book[-2] | 倒数第二本书 |
$…book[0,1] | 前两本书 |
$…book[:2] | 从索引 0(含)到索引 2(不含)的所有图书 |
$…book[1:2] | 从索引 1(含)到索引 2(不含)的所有书籍 |
$…book[-2:] | 最后两本书 |
$…book[2:] | 从索引 2(含)到最后的所有书籍 |
$…book[?(@.isbn)] | 所有有 ISBN 编号的书籍 |
$.store.book[?(@.price < 10)] | 店内所有书籍价格均低于 10 |
Misplaced @ $..book[?(@.price <= $ [‘expensive’])] |
获取json中book数组中price<=$[‘expensive’]结果的所有值 |
$…book[?(@.author =~ /.*REES/i)] | 与 regex 匹配的所有图书(忽略大小写) |
$…* | 给我一切 |
$…book.length() | 书籍数量 |
使用 JsonPath 最简单直接的方法是通过静态读取 API。
String json = "...";
List<String> authors = JsonPath.read(json, "$.store.book[*].author");
如果你只想读取一次,这没有问题。如果您还需要读取其他路径,这就不是办法了,因为每次调用 JsonPath.read(…) 都会对文档进行解析。为了避免这个问题,可以先解析 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 还提供了流畅的应用程序接口。这也是最灵活的一种。
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);
在 Java 中使用 JsonPath 时,了解结果的预期类型非常重要。JsonPath 会自动尝试将结果转换为调用者期望的类型。
//Will throw an java.lang.ClassCastException
List<String> list = JsonPath.parse(json).read("$.store.book[0].author")
//Works fine
String author = JsonPath.parse(json).read("$.store.book[0].author")
在评估一条路径时,你需要了解一条路径何时是确定的这一概念。如果一条路径包含以下内容,那么它就是不确定的:
..
- a deep scan operator?()
- 表达式[, (, )]
- 多个数组索引无限路径总是返回一个列表(由当前 JsonProvider 表示)。
默认情况下,MappingProvider SPI 会提供一个简单对象映射器。这允许你指定想要的返回类型,而 MappingProvider 会尝试执行映射。下面的示例演示了 Long 和 Date 之间的映射。
String json = "{\"date_as_long\" : 1411455611975}";
Date date = JsonPath.parse(json).read("$['date_as_long']", Date.class);
如果将 JsonPath 配置为使用 JacksonMappingProvider、GsonMappingProvider 或 JakartaJsonProvider,甚至可以将 JsonPath 输出直接映射到 POJO 中。
Book book = JsonPath.parse(json).read("$.store.book[0]", Book.class);
要获取完整的类属类型信息,请使用 TypeRef。
TypeRef<List<String>> typeRef = new TypeRef<List<String>>() {};
List<String> titles = JsonPath.parse(JSON_DOCUMENT).read("$.store.book[*].title", typeRef);
在 JsonPath 中创建过滤谓词有三种不同的方法。
内联谓词是在路径中定义的谓词。
List<Map<String, Object>> books = JsonPath.parse(json)
.read("$.store.book[?(@.price < 10)]");
您可以使用 && 和 || 组合多个谓词 [?(@.price < 10 && @.category == ‘fiction’)] , [?(@.category == ‘reference’ || @.price > 10)]。
您可以使用 ! 来否定一个谓词 [?(!(@.price < 10 && @.category == ‘fiction’))] 。
如下所示,可以使用过滤器 API 构建谓词:
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);
注意路径中过滤器的占位符 ? 当提供多个过滤器时,它们将按顺序应用,其中占位符的数量必须与提供的过滤器数量相匹配。您可以在一个过滤器操作中指定多个谓词占位符[?, ?],但两个谓词必须匹配。
过滤器还可以与 "OR "和 "AND "组合使用。
Filter fooOrBar = filter(
where("foo").exists(true)).or(where("bar").exists(true)
);
Filter fooAndBar = filter(
where("foo").exists(true)).and(where("bar").exists(true)
);
第三种方法是实现自己的谓词
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);
在 Goessner 的实现中,JsonPath 可以返回 Path 或 Value。Value 是默认值,也是上面所有示例的返回值。如果你更希望得到我们的查询所命中的元素的路径,这可以通过一个选项来实现。
Configuration conf = Configuration.builder()
.options(Option.AS_PATH_LIST).build();
List<String> pathList = using(conf).parse(json).read("$..author");
assertThat(pathList).containsExactly(
"$['store']['book'][0]['author']",
"$['store']['book'][1]['author']",
"$['store']['book'][2]['author']",
"$['store']['book'][3]['author']");
标准库提供了设置值的可能性。
String newJson = JsonPath.parse(json).set("$['store']['book'][0]['author']", "Paul").jsonString();
创建 "配置 "时,有几个选项标志可以改变默认行为。
该选项使 JsonPath 在缺少叶时返回空值。请看下面的 json
[
{
"name" : "john",
"gender" : "male"
},
{
"name" : "ben"
}
]
Configuration conf = Configuration.defaultConfiguration();
//Works fine
String gender0 = JsonPath.using(conf).parse(json).read("$[0]['gender']");
//PathNotFoundException thrown
String gender1 = JsonPath.using(conf).parse(json).read("$[1]['gender']");
Configuration conf2 = conf.addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL);
//Works fine
String gender0 = JsonPath.using(conf2).parse(json).read("$[0]['gender']");
//Works fine (null is returned)
String gender1 = JsonPath.using(conf2).parse(json).read("$[1]['gender']");
该选项用于配置 JsonPath,即使路径是确定的,也会返回一个列表。
Configuration conf = Configuration.defaultConfiguration();
//ClassCastException thrown
List<String> genders0 = JsonPath.using(conf).parse(json).read("$[0]['gender']");
Configuration conf2 = conf.addOptions(Option.ALWAYS_RETURN_LIST);
//Works fine
List<String> genders0 = JsonPath.using(conf2).parse(json).read("$[0]['gender']");
该选项确保路径评估不会传播异常。它遵循以下简单规则
该选项用于配置 JsonPath,使其在评估不确定路径时要求使用路径中定义的属性。
Configuration conf = Configuration.defaultConfiguration();
//Works fine
List<String> genders = JsonPath.using(conf).parse(json).read("$[*]['gender']");
Configuration conf2 = conf.addOptions(Option.REQUIRE_PROPERTIES);
//PathNotFoundException thrown
List<String> genders = JsonPath.using(conf2).parse(json).read("$[*]['gender']");
JsonPath 提供五种不同的 JsonProviders:
只有在应用程序初始化时,才能按演示更改配置默认值。强烈不建议在运行期间更改配置,尤其是在多线程应用程序中。
Configuration.setDefaults(new Configuration.Defaults() {
private final JsonProvider jsonProvider = new JacksonJsonProvider();
private final MappingProvider mappingProvider = new JacksonMappingProvider();
@Override
public JsonProvider jsonProvider() {
return jsonProvider;
}
@Override
public MappingProvider mappingProvider() {
return mappingProvider;
}
@Override
public Set<Option> options() {
return EnumSet.noneOf(Option.class);
}
});
请注意,JacksonJsonProvider 需要 com.fasterxml.jackson.core:jackson-databind:2.4.5 的类路径,而 GsonJsonProvider 需要 com.google.code.gson:gson:2.3.1 的类路径。
Jakarta EE 9 JSON-P (JSR-342) 和 JSON-B (JSR-367) 提供程序至少需要 Java 8,并要求应用程序运行时类路径上有兼容的 JSON API 实现(如 Eclipse Glassfish 和 Eclipse Yasson);Java EE 应用程序容器也可能提供此类实现。还请注意,Apache Johnzon 尚不兼容 Jakarta EE 9 规范的类路径,如果选择 JSON-B 映射提供程序,则还必须配置和使用 JSON-P 提供程序。
Jakarta EE 9 关于 JSON 处理和数据库绑定(映射)的规范有一个特点,即 Json 数组和对象在完全解析或写入后具有不变性。为了遵守 API 规范,同时允许 JsonPath 通过添加、设置/输入、替换和删除操作来修改 Json 文档,JakartaJsonProvider 必须使用可选的 true 参数进行 initiliazed:
无论采用哪种启动模式,都支持使用 JsonPath 进行的所有查找和读取操作。默认模式所需的内存更少,性能更高。
JsonPath 2.1.0 引入了新的缓存 SPI。这允许 API 用户根据自己的需要配置路径缓存。缓存必须在首次访问前配置好,否则会产生 JsonPathException 异常。JsonPath 有两种缓存实现
如果您想实现自己的缓存,API 也很简单。
CacheProvider.setCache(new Cache() {
//Not thread safe simple cache
private Map<String, JsonPath> map = new HashMap<String, JsonPath>();
@Override
public JsonPath get(String key) {
return map.get(key);
}
@Override
public void put(String key, JsonPath jsonPath) {
map.put(key, jsonPath);
}
});
*
通配操作符,表示获取当前作用对象的 所有 子成员。*~
内置函数,表示获取当前可迭代对象所有子对象的名称。min()
内置函数,表示获取当前可迭代对象子对象的最小值。max()
内置函数,表示获取当前可迭代对象子对象的最大值。sum()
内置函数,表示获取当前可迭代对象子对象之和。concat()
内置函数,表示连接多个对象并生成字符串。在某些场景下,需要在数据处理中对 JSON 结构体的多个对象进行合并整合处理,以便投递到下游进行下一步操作。考虑如下格式:
{
"data": {
"Response": {
"SubnetSet": [
{
"VpcId": "vpc-xxxxxxxx",
"SubnetId": "subnet-xxxxxxxx",
"SubnetName": "ckafka_cloud_subnet-1",
"CidrBlock": "10.0.0.0/19",
"Ipv6CidrBlock": "",
"IsDefault": false,
"IsRemoteVpcSnat": false,
"EnableBroadcast": false,
"Zone": "ap-changsha-ec-1",
"RouteTableId": "rtb-xxxxxxxx",
"NetworkAclId": "",
"TotalIpAddressCount": 8189,
"AvailableIpAddressCount": 8033,
"CreatedTime": "2021-01-25 17:31:00",
"TagSet": [],
"CdcId": "",
"IsCdcSubnet": 0,
"LocalZone": false,
"IsShare": false
}
],
"TotalCount": 1,
"RequestId": "705c4955-0cd9-48b2-9132-79eadae2e3e6"
}
},
"code": 0
}
当下游不具有计算功能,需要在数据处理中聚合 Vpc 以及子网属性时,可以使用 JSONPath 中的 concat()
函数进行多个字段的聚合,并且在此基础上对字符串进行修改。
例如可以使用 $.concat($.data.Response.SubnetSet[0].VpcId,"#",$.data.Response.SubnetSet[0].SubnetId,"#",$.data.Response.SubnetSet[0].CidrBlock))
语法拼接 Vpc 和子网的属性,并且通过 #
字符加以分割。
运行结果如下所示,可以看出在测试结果中已经成功获取整合了 Vip 相关的资源信息:
vpc-xxxxxxxx#subnet-xxxxxxxx#10.0.0.0/19