当前位置:   article > 正文

JsonPath详解

jsonpath

JsonPath

1、概述

JsonPath是用来解析多层嵌套的JSON数据。可以认为JsonPath就是JSON版本的XPath。它是一种信息抽取类库,是从JSON文档中抽取指定信息的工具。JsonPath对于JSON来说,就相当于XPath之于XML。

JsonPath在线解析:https://jsonpath.com/

2、JsonPath语法

2.1、操作符

语法含义
$表示根节点
@当前节点
.<节点名称>获取子节点
[<节点名称1>(,<节点名称2>)]获取子节点,与点号不同,这里可以获取多个子节点
*匹配所有元素节点
..获取子孙节点,无论嵌套多少层,都可以获取到
[<下标1>(,<下标2>)]表示一个或多个下标
[start:end:step]表示切片语法
[?(<表达式>)]过滤器表达式,表达式结果必须是布尔值
()支持表达式计算

2.2、函数

可以在JsonPath表达式执行后进行调用,其输入表达式的结果。

函数描述输出
min()提供数字数组的最小值Double
max()提供数字数组的最大值Double
avg()提供数字数据的平均值Double
stddev()提供数字数组的标准偏差值Double
length()提供数组的长度Integer
sum()提供数字数组的和值Double
keys()提供属性键(终端自键~的替代选择)Set<E>
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

2.3、过滤器

过滤器是用于过滤数组的逻辑表达式,一个通常的表达式形如:[?(@.age > 18)],可以通过逻辑表达式&&||组合多个过滤器表达式,例如[?(@.price < 10 && @.category == ‘fiction’)],字符串必须用单引号包围,例如[?(@.color == ‘blue’)]

操作符描述
==等于符号,但数字1不等于字符1(1 is not equal to ‘1’)
!=不等于
<小于
<=小于等于
>大于等于
>=大于等于
=~判断是否符合正则表达式,例如[?(@.name =~ /foo.*?/i)]
in属于,例如[?(@.size in ['S', 'M'])]
nin排除符号
size左侧(数组或字符串)的大小应与右侧一致
empty数组或字符串)应为空
subsetof左是右的子集 [?(@.sizes subsetof [‘S’, ‘M’, ‘L’])]
anyof左与右有交集 [?(@.sizes anyof [‘M’, ‘L’])]
noneof左边与右边没有交集 [?(@.sizes noneof [‘M’, ‘L’])]

2.4、示例

{
    "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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
JsonPathResult
$.store.book[*].author所有书籍的作者
$..author所有作者
$..book[2]第3本数
$..book[0,1]前2本数
$..book[1:3]切片操作,从索引 1(含)到索引 3(不含)的所有书籍
$..book[-1:]倒数第一本数
$..book[?(@.isbn)]所有有 ISBN 编号的书籍
$.store.book[?(@.price < 10)]价格低于10的所有数据
$..book.length书籍的数量

3、基于Java的使用

Jayway JsonPath则提供了Java版本的实现,方便开发者进行集成使用。

Maven依赖:

<!--Json Path-->
<dependency>
  <groupId>com.jayway.jsonpath</groupId>
  <artifactId>json-path</artifactId>
  <version>2.7.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.1、使用方式

使用 JsonPath 最简单直接的方法是通过静态读取 API。

public class Demo {

    private String jsonStr = "{\n" +
            "  \"store\": {\n" +
            "    \"book\": [\n" +
            "      {\n" +
            "        \"category\": \"reference\",\n" +
            "        \"author\": \"Nigel Rees\",\n" +
            "        \"title\": \"Sayings of the Century\",\n" +
            "        \"price\": 8.95\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"Evelyn Waugh\",\n" +
            "        \"title\": \"Sword of Honour\",\n" +
            "        \"price\": 12.99\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"Herman Melville\",\n" +
            "        \"title\": \"Moby Dick\",\n" +
            "        \"isbn\": \"0-553-21311-3\",\n" +
            "        \"price\": 8.99\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"J. R. R. Tolkien\",\n" +
            "        \"title\": \"The Lord of the Rings\",\n" +
            "        \"isbn\": \"0-395-19395-8\",\n" +
            "        \"price\": 22.99\n" +
            "      }\n" +
            "    ],\n" +
            "    \"bicycle\": {\n" +
            "      \"color\": \"red\",\n" +
            "      \"price\": 19.95\n" +
            "    }\n" +
            "  },\n" +
            "  \"expensive\": 10\n" +
            "}";

    @Test
    public void test1() {
        List<String> authors = JsonPath.read(jsonStr, "$.store.book[*].author");
        //["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]
        System.out.println(authors);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

如果你只读取一次,这没有问题。如果还需要读取其他路径,这就不是办法了,因为每次调用 JsonPath.read(…) 都会对文档进行解析。为了避免这个问题,可以先解析 json。

@Test
public void test2() {
    final Object document = Configuration.defaultConfiguration().jsonProvider().parse(jsonStr);

    String author0 = JsonPath.read(document, "$.store.book[0].author");
    String author1 = JsonPath.read(document, "$.store.book[1].author");
    //Nigel Rees
    System.out.println(author0);
    //Evelyn Waugh
    System.out.println(author1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

JsonPath还提供了流畅的应用程序接口。这也是最灵活的一种:

@Test
public void test3() {
    DocumentContext ctx = JsonPath.parse(jsonStr);
    List<String> authorsOfBooksWithISBN = ctx.read("$.store.book[?(@.isbn)].author");
    //["Herman Melville","J. R. R. Tolkien"]
    System.out.println(authorsOfBooksWithISBN);

    //[{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99},
    // {"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}]
    List<Map<String, Object>> expensiveBooks = JsonPath.using(Configuration.defaultConfiguration())
            .parse(jsonStr)
            .read("$.store.book[?(@.price > 10)]", List.class);
    System.out.println(expensiveBooks);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.2、返回类型

在 Java 中使用 JsonPath 时,了解结果的预期类型非常重要。JsonPath 会自动尝试将结果转换为调用者期望的类型。

//Nigel Rees
String author = JsonPath.parse(jsonStr).read("$.store.book[0].author");

//java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List
List<String> list = JsonPath.parse(jsonStr).read("$.store.book[0].author");
  • 1
  • 2
  • 3
  • 4
  • 5

在评估一条路径时,你需要了解一条路径何时是确定的这一概念。如果一条路径包含以下内容,那么它就是不确定的:

  • ..:扫描操作符
  • ?(<expression>):表达式
  • [<number>, <number>(, <number)]:多个数组索引

无限路径总是返回一个列表(由当前 JsonProvider 表示)。

默认情况下,MappingProvider SPI 会提供一个简单对象映射器。这允许你指定想要的返回类型,而 MappingProvider 会尝试执行映射。下面的示例演示了 Long 和 Date 之间的映射。

String json = "{\"date_as_long\":  1411455611975}";
Date date = JsonPath.parse(json).read("$['date_as_long']", Date.class);
//Tue Sep 23 15:00:11 CST 2014
System.out.println(date);
  • 1
  • 2
  • 3
  • 4

如果将 JsonPath 配置为使用 JacksonMappingProvider、GsonMappingProvider 或 JakartaJsonProvider,甚至可以将 JsonPath 输出直接映射到 POJO 中。

Book book = JsonPath.parse(jsonStr).read("$.store.book[0]", Book.class);
//Book{category='reference', title='Sayings of the Century', author='Nigel Rees', price=8.95, isbn='null'}
System.out.println(book);
  • 1
  • 2
  • 3

要获取完整的类属类型信息,请使用 TypeRef。

//注意,默认的Json-smart provider不支持TypeRef,可以使用Jackson或Gson Provider
TypeRef<List<String>> typeRef = new TypeRef<List<String>>() {};

List<String> titles = JsonPath.parse(jsonStr).read("$.store.book[*].title", typeRef);
System.out.println(titles);
  • 1
  • 2
  • 3
  • 4
  • 5

3.3、JsonProvider

在Jayway JsonPath中提供了多种JsonProvider,其中默认的为JsonSmartJsonProvider。这里我们期望能够直接对读取的数据进行反序列化,这里我们选用JacksonJsonProvider,此时要求jackson-databind依赖的版本至少为2.4.5。

<!--Jackson-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.3</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class Book {
    private String category;

    private String title;

    private String author;

    private Double price;

    private String isbn;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
public class JsonProviderTest {

    private String jsonStr = "{\n" +
            "  \"store\": {\n" +
            "    \"book\": [\n" +
            "      {\n" +
            "        \"category\": \"reference\",\n" +
            "        \"author\": \"Nigel Rees\",\n" +
            "        \"title\": \"Sayings of the Century\",\n" +
            "        \"price\": 8.95\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"Evelyn Waugh\",\n" +
            "        \"title\": \"Sword of Honour\",\n" +
            "        \"price\": 12.99\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"Herman Melville\",\n" +
            "        \"title\": \"Moby Dick\",\n" +
            "        \"isbn\": \"0-553-21311-3\",\n" +
            "        \"price\": 8.99\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"J. R. R. Tolkien\",\n" +
            "        \"title\": \"The Lord of the Rings\",\n" +
            "        \"isbn\": \"0-395-19395-8\",\n" +
            "        \"price\": 22.99\n" +
            "      }\n" +
            "    ],\n" +
            "    \"bicycle\": {\n" +
            "      \"color\": \"red\",\n" +
            "      \"price\": 19.95\n" +
            "    }\n" +
            "  },\n" +
            "  \"expensive\": 10\n" +
            "}";


    @Test
    public void test() {
        //使用JacksonJsonProvider
        Configuration configuration = Configuration
                .builder()
                .mappingProvider(new JacksonMappingProvider())
                .build();

        ReadContext ctx = JsonPath.using(configuration).parse(jsonStr);

        TypeRef<List<Book>> typeRef = new TypeRef<List<Book>>() {};
        List<Book> books = ctx.read("$.store.book[*]", typeRef);

        books.forEach(System.out::println);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
Book{category='reference', title='Sayings of the Century', author='Nigel Rees', price=8.95, isbn='null'}
Book{category='fiction', title='Sword of Honour', author='Evelyn Waugh', price=12.99, isbn='null'}
Book{category='fiction', title='Moby Dick', author='Herman Melville', price=8.99, isbn='0-553-21311-3'}
Book{category='fiction', title='The Lord of the Rings', author='J. R. R. Tolkien', price=22.99, isbn='0-395-19395-8'}
  • 1
  • 2
  • 3
  • 4

3.4、谓词

在JsonPath中创建过滤器谓词有三种不同的方法:

  • 内联谓词
  • 过滤器谓词
  • 自定义谓词
1、内联谓词

内联谓词是在路径中定义的谓词。

可以使用 &&|| 组合多个谓词 [?(@.price < 10 && @.category == ‘fiction’)] , [?(@.category == ‘reference’ || @.price > 10)]

可以使用! 来否定一个谓词 [?(!(@.price < 10 && @.category == ‘fiction’))]

@Test
public void test1() {
    List<Map<String, Object>> books = JsonPath.parse(jsonStr)
            .read("$.store.book[?(@.price < 10)]");
    books.forEach(System.out::println);
    //{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}


    books = JsonPath.parse(jsonStr)
            .read("$.store.book[?(!@.price < 10)]");
    books.forEach(System.out::println);
    //{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}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
2)、过滤器谓词

如下所示,可以使用过滤器 API 构建谓词:

@Test
public void test2() {
    Filter cheapFictionFilter = Filter.filter(
            Criteria.where("price").lte(10)
    );
    //注意路径中过滤器的占位符 ?
    //当提供多个过滤器时,它们将按顺序应用,其中占位符的数量必须与提供的过滤器数量相匹配。
    //可以在一个过滤器操作中指定多个谓词占位符[?, ?],但两个谓词必须匹配。
    List<Map<String, Object>> books = JsonPath.parse(jsonStr)
            .read("$.store.book[?]", cheapFictionFilter);
    books.forEach(System.out::println);
    //{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}

    //过滤器还可以与OR和AND组合使用
    Filter f = Filter.filter(Criteria.where("price").lte(10))
            .and(Criteria.where("isbn").exists(true));

    books = JsonPath.parse(jsonStr)
            .read("$.store.book[?]", f);
    books.forEach(System.out::println);
    //{category=fiction, author=Herman Melville, title=Moby Dick, isbn=0-553-21311-3, price=8.99}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
3)、自定义谓词
@Test
public void test3() {
    //自定义谓词
    Predicate booksWithISBN = new Predicate() {
        @Override
        public boolean apply(PredicateContext ctx) {
            return ctx.item(Map.class).containsKey("isbn");
        }
    };
    List<Map<String, Object>> books = JsonPath.parse(jsonStr)
            .read("$.store.book[?]", booksWithISBN);
    books.forEach(System.out::println);
    //{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}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
4)、示例
{
    "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
        },
        "clothes":[
            {
                "name":"牛仔裤",
                "sizes":"S",
                "price":94
            },
            {
                "name":"背心",
                "sizes":"M",
                "price":48
            },
            {
                "name":"裙子",
                "sizes":["S", "M"],
                "price":1.24
            },
            {
                "name":"羊毛衫",
                "sizes":["XS", "XL"],
                "price":78.99
            },
            {
                "name":"Polo衫",
                "sizes":["XS", "XL", "M"],
                "price":18.99
            }
        ]
    },
    "expensive":10
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
@Test
public void test() {
    //使用JacksonJsonProvider
    Configuration configuration = Configuration
            .builder()
            .mappingProvider(new JacksonMappingProvider())
            .build();
    ReadContext ctx = JsonPath.using(configuration)
            .parse(jsonStr);


    //方式一:内联谓词
    TypeRef<List<Clothes>> typeRef = new TypeRef<List<Clothes>>() {};
    final List<Clothes> clothes1 = ctx.read("$.store.clothes[?(@.price > 50 || @.sizes anyof ['M'])]", typeRef);
    System.out.println("************** clothes1 ***************");
    clothes1.forEach(System.out::println);

    //方式二:Filter谓词
    Filter filter = Filter.filter(Criteria.where("price").gt(50))
            .or(Criteria.where("sizes").anyof(Arrays.asList("M")));
    //使用谓词占位符?
    Clothes[] clothes2 = ctx.read("$.store.clothes[?]", Clothes[].class, filter);
    System.out.println("************** clothes2 ***************");
    for (Clothes clothes : clothes2) {
        System.out.println(clothes);
    }

    //方式三:自定义谓词
    Predicate rule = (context) -> {
        final Map map = context.item(Map.class);
        boolean b1 = false;
        Object priceObj = map.getOrDefault("price", null);
        if (priceObj != null) {
            String priceStr = priceObj.toString();
            Double price = 0d;
            try {
                price = Double.parseDouble(priceStr);
            } catch (Exception e) {

            }
            b1 = price > 50d;
        }

        boolean b2 = false;
        Object sizes = map.getOrDefault("sizes", null);
        if (sizes != null && sizes instanceof List) {
            List<String> sizeList = (List<String>) sizes;
            List<String> targetList = Arrays.asList("M");
            for (String size : sizeList) {
                if (targetList.contains(size)) {
                    b2 = true;
                    break;
                }
            }
        }
        return b1 || b2;
    };
    // 使用谓词的占位符?
    Clothes[] clothes3 = ctx.read("$.store.clothes[?]", Clothes[].class, rule);
    System.out.println("-------------- clothes3 ---------------");
    for (Clothes clothes : clothes3) {
        System.out.println(clothes);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
************** clothes1 ***************
Clothes{name='牛仔裤', price=94.0, sizes=S}
Clothes{name='裙子', price=1.24, sizes=[S, M]}
Clothes{name='羊毛衫', price=78.99, sizes=[XS, XL]}
Clothes{name='Polo衫', price=18.99, sizes=[XS, XL, M]}
************** clothes2 ***************
Clothes{name='牛仔裤', price=94.0, sizes=S}
Clothes{name='裙子', price=1.24, sizes=[S, M]}
Clothes{name='羊毛衫', price=78.99, sizes=[XS, XL]}
Clothes{name='Polo衫', price=18.99, sizes=[XS, XL, M]}
-------------- clothes3 ---------------
Clothes{name='牛仔裤', price=94.0, sizes=S}
Clothes{name='裙子', price=1.24, sizes=[S, M]}
Clothes{name='羊毛衫', price=78.99, sizes=[XS, XL]}
Clothes{name='Polo衫', price=18.99, sizes=[XS, XL, M]}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3.5、使用函数

//language=JSON
String jsonStr = "{\n" +
        "  \"name\": \"tom\",\n" +
        "  \"age\": 18,\n" +
        "  \"height\": 1.77,\n" +
        "  \"scores\": [1.1, 2.2, 3,3, 4.4, 5.5, 6.6]\n" +
        "}";
ReadContext ctx = JsonPath.using(Configuration.defaultConfiguration()).parse(jsonStr);

Double min = ctx.read("$.scores.min()");
System.out.println(min);//1.1
Double max = ctx.read("$.scores.max()");
System.out.println(max);//6.6
Double avg = ctx.read("$.scores.avg()");
System.out.println(avg);//3.6857142857142864
Double stddev = ctx.read("$.scores.stddev()");
System.out.println(stddev);//1.7779832647682354
Double sum = ctx.read("$.scores.sum()");
System.out.println(sum);//25.800000000000004
Integer length = ctx.read("$.scores.length()");
System.out.println(length);//7
Set<String> keys = ctx.read("$.keys()");
System.out.println(keys);//[name, age, height, scores]


String concat = ctx.read("$.concat(@.name, \" \", @.age)");
System.out.println(concat);//tom 18

List<Double> scores = ctx.read("$.scores.append(99.9)");
System.out.println(scores);//[1.1,2.2,3,3,4.4,5.5,6.6,99.9]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3.6、返回路径

JsonPath 可以返回 Path 或 Value。Value 是默认值,也是上面所有示例的返回值。如果你更希望得到我们的查询所命中的元素的路径,这可以通过一个选项来实现。

//返回路径
Configuration configuration = Configuration
        .builder()
        .options(Option.AS_PATH_LIST)
        .build();

List<String> pathList = JsonPath.using(configuration).parse(jsonStr).read("$..author");
System.out.println(pathList);
//["$['store']['book'][0]['author']",
// "$['store']['book'][1]['author']",
// "$['store']['book'][2]['author']","
// $['store']['book'][3]['author']"]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.7、添加/设置值

String str = "{\"name\": \"tom\"}";

//设置值
String newStr1 = JsonPath.parse(str)
        .set("$.name", "jerry")
        .jsonString();

//添加
String newStr2 = JsonPath.parse(newStr1)
        .put("$", "age",17)
        .jsonString();

//{"name":"jerry","age":17}
System.out.println(newStr2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.8、配置

1)、Options

有几个选项标志可以改变JsonPath的默认行为:

  • DEFAULT_PATH_LEAF_TO_NULL:该选项使 JsonPath 在缺少叶子节点时返回NUll值
@Test
public void configTest1() {
    //language=JSON
    String jsonStr = "[\n" +
            "  {\n" +
            "    \"name\" : \"john\",\n" +
            "    \"gender\" : \"male\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\" : \"ben\"\n" +
            "  }\n" +
            "]";

    //默认情况抛出异常
    //com.jayway.jsonpath.PathNotFoundException: No results for path: $[1]['gender']
    String gender1 = JsonPath.parse(jsonStr).read("$[1].gender");

    //使用DEFAULT_PATH_LEAF_TO_NULL,会返回Null
    Configuration configuration = Configuration.builder()
            .options(Option.DEFAULT_PATH_LEAF_TO_NULL)
            .build();
    String gender2 = JsonPath.using(configuration).parse(jsonStr).read("$[1].gender");
    System.out.println(gender2);//null
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • ALWAYS_RETURN_LIST:即使路径是确定的,也会返回一个列表
@Test
public void configTest2() {
    String jsonStr = "[\n" +
            "  {\n" +
            "    \"name\" : \"john\",\n" +
            "    \"gender\" : \"male\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\" : \"ben\"\n" +
            "  }\n" +
            "]";
    //默认情况下抛出异常
    //java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List
    List<String> genders1 = JsonPath.parse(jsonStr).read("$[0].gender");

    //使用ALWAYS_RETURN_LIST,返回一个列表
    Configuration configuration = Configuration.builder()
            .options(Option.ALWAYS_RETURN_LIST)
            .build();
    List<String> genders2 = JsonPath.using(configuration).parse(jsonStr).read("$[0].gender");
    System.out.println(genders2);//["male"]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • SUPPRESS_EXCEPTIONS:该选项确保路径评估不会传播异常。它遵循以下简单规则:
    • 如果存在 ALWAYS_RETURN_LIST 选项,则返回空列表
    • 如果不存在 ALWAYS_RETURN_LIST 选项,则返回Null
@Test
public void configTest3() {
    String jsonStr = "[\n" +
            "  {\n" +
            "    \"name\" : \"john\",\n" +
            "    \"gender\" : \"male\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\" : \"ben\"\n" +
            "  }\n" +
            "]";

    //存在 ALWAYS_RETURN_LIST选项,则返回空列表
    Configuration configuration1 = Configuration.builder()
            .options(Option.ALWAYS_RETURN_LIST, Option.SUPPRESS_EXCEPTIONS)
            .build();
    List<String> genders1 = JsonPath.using(configuration1).parse(jsonStr).read("$[1].gender");
    System.out.println(genders1);//[]

    //不存在 ALWAYS_RETURN_LIST选项,则返回Null
    Configuration configuration2 = Configuration.builder()
            .options(Option.SUPPRESS_EXCEPTIONS)
            .build();
    List<String> genders2 = JsonPath.using(configuration2).parse(jsonStr).read("$[1].gender");
    System.out.println(genders2);//null
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • REQUIRE_PROPERTIES:在评估不确定路径时要求使用路径中定义的属性
    • 如果REQUIRE_PROPERTIES选项存在,则抛出PathNotFoundException
    • 如果REQUIRE_PROPERTIES选项不存在正常返回
@Test
public void configTest4() {
    String jsonStr = "[\n" +
            "  {\n" +
            "    \"name\" : \"john\",\n" +
            "    \"gender\" : \"male\"\n" +
            "  },\n" +
            "  {\n" +
            "    \"name\" : \"ben\"\n" +
            "  }\n" +
            "]";

    //存在 REQUIRE_PROPERTIES,则抛出异常
    //com.jayway.jsonpath.PathNotFoundException: No results for path: $[1]['gender']
    Configuration configuration1 = Configuration.builder()
            .options(Option.REQUIRE_PROPERTIES)
            .build();
    // List<String> genders1 = JsonPath.using(configuration1).parse(jsonStr).read("$[*].gender");

    //不存在 REQUIRE_PROPERTIES,返回["male"]
    Configuration configuration2 = Configuration.builder()
            .options()
            .build();
    List<String> genders2 = JsonPath.using(configuration2).parse(jsonStr).read("$[*].gender");
    System.out.println(genders2);//["male"]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • AS_PATH_LIST:不返回值,返回路径
2)、JsonProvider SPI

JsonPath 提供五种不同的 JsonProviders:

  • JsonSmartJsonProvider (default)
  • JacksonJsonProvider
  • JacksonJsonNodeJsonProvider
  • GsonJsonProvider
  • JsonOrgJsonProvider
  • JakartaJsonProvider

只有在应用程序初始化时,才能按演示更改配置默认值。强烈不建议在运行期间更改配置,尤其是在多线程应用程序中。

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);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

请注意,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:

  • JsonProvider jsonProvider = new JakartaJsonProvider(true)(启用可变 Json 数组和对象)
  • JsonProvider jsonProvider = new JakartaJsonProvider()(默认,严格遵循 JSON-P API)

无论采用哪种启动模式,都支持使用 JsonPath 进行的所有查找和读取操作。默认模式所需的内存更少,性能更高。

3)、Cache SPI

JsonPath 2.1.0 引入了新的缓存 SPI。这允许 API 用户根据自己的需要配置路径缓存。缓存必须在首次访问前配置好,否则会产生 JsonPathException 异常。JsonPath 有两种缓存实现

  • com.jayway.jsonpath.spi.cache.LRUCache(默认,线程安全)
  • com.jayway.jsonpath.spi.cache.NOOPCache(无缓存)

如果您想实现自己的缓存,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);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/1005481
推荐阅读
相关标签
  

闽ICP备14008679号