神奇的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
}
JsonPathResult
$.store.book[*].author
The authors of all books
[ "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ]
$..author
All authors
[ "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ]
$.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);
    }
});

 

 

posted on 2019-10-08 17:41  老街  阅读(1050)  评论(0编辑  收藏  举报

导航