神奇的JsonPath
这篇博文主要参考了JsonPath官方文档https://github.com/json-path/JsonPath,汇总整理的目的是为了未来更方便查阅
JsonPath
类似于XPath在xml文档中的定位,JsonPath表达式通常是用来路径检索或设置Json的。其表达式可以接受“dot–notation”和“bracket–notation”格式,例如$.store.book[0].title、$['store']['book'][0]['title']
运算符
运算符 | 说明 |
$ | 根节点,代表整个json对象 |
@ | 表示当前节点,用在过滤判断中 |
* | 通配符,可以表示任意节点名或数字索引 |
.. | 深度扫描,返回同key的value数组 |
.<name> | 点表达式,表示当前节点的子节点 |
['<name>' (, '<name>')] | 表示一个或多个子节点 |
[<number> (, <number>)] | 通过数组索引来表示 |
[start:end] | 切片运算符 |
[?(<expression>)] | 过滤表达式,表达式结果为布尔值 |
函数
函数在jsonpath表达式的末尾被调用,并且函数以jsonpath表达式的结果作为输入
函数 | 说明 | 输出 |
---|---|---|
min() | 获取一个数值数组的最小值 | Double |
max() | 获取一个数值数组的最大值 | Double |
avg() | 获取一个数值数组的平均值 | Double |
stddev() | 获取一个数值数组的标准差 | Double |
length() | 获取一个数组的长度 | Integer |
过滤器运算符
所谓的过滤器就是逻辑表达式,它用于过滤数组,过滤器的典型形式:[?(@.age > 18)],其中@表示当前节点。多个逻辑表达式之间可以通过&&或者||来连接。字符串常量用单引号或双引号表示:
[?(@.color == 'blue')]
或者 [?(@.color == "blue")]。
运算符 | 说明 |
---|---|
== | 相等运算符 |
!= | 不等运算符 |
< | 小于运算符 |
<= | 小于等于运算符 |
> | 大于运算符 |
>= | 大于等于运算符 |
=~ | left matches regular expression [?(@.name =~ /foo.*?/i)] |
in | 类似于sql语句中的in,[?(@.size in ['S', 'M'])] |
nin | not in |
subsetof | 运算符左边为右边的子集 [?(@.sizes subsetof ['S', 'M', 'L'])] |
anyof | left has an intersection with right [?(@.sizes anyof ['M', 'L'])] |
noneof | left has no intersection with right [?(@.sizes noneof ['M', 'L'])] |
size | size左边的数组或字符串的长度为右边的值 |
empty | 用于字符串或数组的判空 |
实例
{ "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 |
The authors of all books |
$..author |
All authors |
$.store.* |
All things, both books and bicycles [ [ { "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 } ], { "color":"red", "price":19.95 } ] |
$.store..price |
The price of everything [ 8.95, 12.99, 8.99, 22.99, 19.95 ] |
$..book[2] |
The third book [ { "category":"fiction", "author":"Herman Melville", "title":"Moby Dick", "isbn":"0-553-21311-3", "price":8.99 } ] |
$..book[-2] |
The second to last book [ { "category":"fiction", "author":"Herman Melville", "title":"Moby Dick", "isbn":"0-553-21311-3", "price":8.99 } ] |
$..book[0,1] |
The first two books [ { "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 } ] |
$..book[:2] |
All books from index 0 (inclusive) until index 2 (exclusive) [ { "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 } ] |
$..book[1:2] |
All books from index 1 (inclusive) until index 2 (exclusive) [ { "category":"fiction", "author":"Evelyn Waugh", "title":"Sword of Honour", "price":12.99 } ] |
$..book[-2:] |
Last two books [ { "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 } ] |
$..book[2:] |
Book number two from tail [ { "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 } ] |
$..book[?(@.isbn)] |
All books with an ISBN number [ { "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 } ] |
$.store.book[?(@.price < 10)] |
All books in store cheaper than 10 [ { "category":"reference", "author":"Nigel Rees", "title":"Sayings of the Century", "price":8.95 }, { "category":"fiction", "author":"Herman Melville", "title":"Moby Dick", "isbn":"0-553-21311-3", "price":8.99 } ] |
$..book[?(@.price <= $['expensive'])] |
All books in store that are not "expensive" [ { "category":"reference", "author":"Nigel Rees", "title":"Sayings of the Century", "price":8.95 }, { "category":"fiction", "author":"Herman Melville", "title":"Moby Dick", "isbn":"0-553-21311-3", "price":8.99 } ] |
$..book[?(@.author =~ /.*REES/i)] |
All books matching regex (ignore case) [ { "category":"reference", "author":"Nigel Rees", "title":"Sayings of the Century", "price":8.95 } ] |
$..* |
Give me every thing [ { "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 } }, 10, [ { "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 } ], { "color":"red", "price":19.95 }, { "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 }, "reference", "Nigel Rees", "Sayings of the Century", 8.95, "fiction", "Evelyn Waugh", "Sword of Honour", 12.99, "fiction", "Herman Melville", "Moby Dick", "0-553-21311-3", 8.99, "fiction", "J. R. R. Tolkien", "The Lord of the Rings", "0-395-19395-8", 22.99, "red", 19.95 ] |
$..book.length() |
The number of books [ 4 ] |
api
使用jsonPath的最简单最直接的方法是通过静态方法read()
String json = "...";
String json = "...";
List<String> authors = JsonPath.read(json, "$.store.book[*].author");
If you only want to read once this is OK. In case you need to read an other path as well this is not the way to go since the document will
be parsed every time you call JsonPath.read(...).
To avoid the problem you can parse the json first.
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 also provides a fluent API. This is also the most flexible one.
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")
When evaluating a path you need to understand the concept of when a path is definite
. A path is indefinite
if it contains:
1 .. - a deep scan operator 2 ?(<expression>) - an expression 3 [<number>, <number> (, <number>)] - multiple array indexes
By default a simple object mapper is provided by the MappingProvider SPI. This allows you to specify the return type you want and the MappingProvider will try to perform the mapping. In the example below mapping between Long
and Date
is demonstrated.
String json = "{\"date_as_long\" : 1411455611975}"; Date date = JsonPath.parse(json).read("$['date_as_long']", Date.class);
If you configure JsonPath to use JacksonMappingProvider
or GsonMappingProvider
you can even map your JsonPath output directly into POJO's.
Book book = JsonPath.parse(json).read("$.store.book[0]", Book.class);
To obtainin full generics type information, use TypeRef.
TypeRef<List<String>> typeRef = new TypeRef<List<String>>() {};
List<String> titles = JsonPath.parse(JSON_DOCUMENT).read("$.store.book[*].title", typeRef);
Predicates
There are three different ways to create filter predicates in JsonPath.
Inline Predicates
Inline predicates are the ones defined in the path.
List<Map<String, Object>> books = JsonPath.parse(json)
.read("$.store.book[?(@.price < 10)]");
You can use &&
and ||
to combine multiple predicates [?(@.price < 10 && @.category == 'fiction')]
, [?(@.category == 'reference' || @.price > 10)]
.
You can use !
to negate a predicate [?(!(@.price < 10 && @.category == 'fiction'))]
.
Filter Predicates
Predicates can be built using the Filter API as shown below:
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);
Notice the placeholder ?
for the filter in the path. When multiple filters are provided they are applied in order where the number of placeholders must match the number of provided filters. You can specify multiple predicate placeholders in one filter operation [?, ?]
, both predicates must match.
Filters can also be combined with 'OR' and '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)
);
Roll Your Own
Third option is to implement your own predicates
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);
Path vs Value
JsonPath默认返回值,事实上可以返回路径,通过可选配置可以获取满足我们查询结果的路径
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import java.util.List;
public class JsonPathDemo {
public static void main(String[] args) {
String json = "[\n" +
" {\n" +
" \"name\" : \"john\",\n" +
" \"gender\" : \"male\"\n" +
" },\n" +
" {\n" +
" \"name\" : \"ben\"\n" +
" }\n" +
"]";
Configuration conf = Configuration.builder().options(Option.AS_PATH_LIST).build();
List<String> pathList = JsonPath.using(conf).parse(json).read("$..name");
for (String jsonPath : pathList) {
System.out.println(jsonPath);
}
}
}
控制台打印结果:
$[0]['name']
$[1]['name']
Tweaking Configuration
Options
When creating your Configuration there are a few option flags that can alter the default behaviour.
DEFAULT_PATH_LEAF_TO_NULL
This option makes JsonPath return null for missing leafs. Consider the following 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']");
ALWAYS_RETURN_LIST
This option configures JsonPath to return a list even when the path is definite
.
Configuration conf = Configuration.defaultConfiguration(); //Works fine List<String> genders0 = JsonPath.using(conf).parse(json).read("$[0]['gender']"); //PathNotFoundException thrown List<String> genders1 = JsonPath.using(conf).parse(json).read("$[1]['gender']");
SUPPRESS_EXCEPTIONS
This option makes sure no exceptions are propagated from path evaluation. It follows these simple rules:
- If option
ALWAYS_RETURN_LIST
is present an empty list will be returned - If option
ALWAYS_RETURN_LIST
is NOT present null returned
JsonProvider SPI
JsonPath is shipped with five different JsonProviders:
1 JsonSmartJsonProvider (default) 2 JacksonJsonProvider 3 JacksonJsonNodeJsonProvider 4 GsonJsonProvider 5 JsonOrgJsonProvider
Changing the configuration defaults as demonstrated should only be done when your application is being initialized. Changes during runtime is strongly discouraged, especially in multi threaded applications.
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); } });
Note that the JacksonJsonProvider requires com.fasterxml.jackson.core:jackson-databind:2.4.5
and the GsonJsonProvider requires com.google.code.gson:gson:2.3.1
on your classpath.
Cache SPI
在JsonPath 2.1.0中引入了一个新的Cache SPI。The cache must be configured before it is accesses for the first time or a JsonPathException is thrown. 它容许API调用按照他们自己的需求来配置路径缓存。JsonPath提供两种缓存实现
1 com.jayway.jsonpath.spi.cache.LRUCache (default, thread safe) 2 com.jayway.jsonpath.spi.cache.NOOPCache (no cache)
If you want to implement your own cache the API is simple.
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); } });