17、使用 JsonPath 提取 JSON 值

使用 JsonPath 提取 JSON 值

1. 获取和使用 JsonPath 库

可以从中央 Maven 仓库(http://search.maven.org/ )获取 JsonPath。
- Maven 项目 :如果你熟悉 Maven,可以将以下 XML 片段添加到依赖于 JsonPath 的 Maven 项目的项目对象模型(POM)文件中:

<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>2.2.0</version>
</dependency>
  • 非 Maven 项目 :如果不使用 Maven,可以下载 JsonPath Jar 文件以及它所依赖的所有 Jar 文件,然后将这些 Jar 文件添加到类路径中。最简单的下载方式是访问 https://github.com/jayway/JsonPath/releases ,下载 json-path-2.2.0 - SNAPSHOT - with - dependencies.zip 。

下载并解压 Zip 文件后,会发现 json - path - 2.2.0 - SNAPSHOT - with - dependencies 主目录下有以下子目录:
| 子目录 | 说明 |
| — | — |
| api | 包含基于 Javadoc 的 JsonPath API 文档 |
| lib | 包含为了使用 JsonPath 而需要添加到类路径的 Jar 文件,并非所有情况都需要所有的 Jar 文件,但最好都包含在类路径中 |
| lib - optional | 用于配置 JsonPath 的可选 Jar 文件 |
| source | 包含 JsonPath API 的 Java 源代码的 Jar 文件 |

编译访问 JsonPath 的 Java 源代码时,类路径中只需包含 json - path - 2.2.0 - SNAPSHOT.jar:

javac -cp json-path-2.2.0-SNAPSHOT.jar source file

运行访问 JsonPath 的应用程序时,使用以下命令行:

java -cp accessors-smart-1.1.jar;asm-5.0.3.jar;json-path-2.2.0-SNAPSHOT.jar;json-smart-2.2.1.jar;slf4j-api-1.7.16.jar;tapestry-json-5.4.0.jar;. main classfile
2. 探索 JsonPath 库

JsonPath 库被组织成多个包,通常会与 com.jayway.jsonpath 包及其类型进行交互。以下是提取 JSON 对象值和使用谓词过滤项的相关内容。

3. 从 JSON 对象中提取值

com.jayway.jsonpath 包提供了 JsonPath 类作为使用 JsonPath 库的入口点。以下是一个示例代码:

import java.util.HashMap;
import java.util.List;
import com.jayway.jsonpath.JsonPath;

public class JsonPathDemo {
    public static void main(String[] args) {
        String 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" +
                        "         }" +
                        "      ]," +
                        "      \"bicycle\":" +
                        "      {" +
                        "         \"color\": \"red\"," +
                        "         \"price\": 19.95" +
                        "      }" +
                        "   }" +
                        "}";
        JsonPath path = JsonPath.compile("$.store.book[1]");
        HashMap books = path.read(json);
        System.out.println(books);

        List<Object> authors = JsonPath.read(json, "$.store.book[*].author");
        System.out.println(authors);

        String author = JsonPath.read(json, "$.store.book[1].author");
        System.out.println(author);
    }
}

上述代码中, main() 方法的操作步骤如下:
1. 声明一个基于字符串的 JSON 对象,并将其引用赋值给变量 json
2. 调用 JsonPath.compile(String jsonPath, Predicate... filters) 静态方法来编译 JsonPath 表达式,以提高性能,并将编译结果作为 JsonPath 对象返回。
3. 调用 <T> T read(String json) 泛型方法,该方法在之前编译的 JsonPath 实例上调用,接收基于字符串的 JSON 对象作为参数,并将编译后的 JsonPath 表达式应用于该参数。
4. 也可以使用 JsonPath 的静态 read() 方法,例如 <T> T read(String json, String jsonPath, Predicate... filters) ,该方法为 jsonPath 参数创建一个新的 JsonPath 对象并将其应用于 json 字符串。

编译和运行上述代码的命令如下:

javac -cp json-path-2.2.0-SNAPSHOT.jar JsonPathDemo.java
java -cp accessors-smart-1.1.jar;asm-5.0.3.jar;json-path-2.2.0-SNAPSHOT.jar;json-smart-2.2.1.jar;slf4j-api-1.7.16.jar;tapestry-json-5.4.0.jar;. JsonPathDemo

预期输出如下:

{category=fiction, author=Evelyn Waugh, title=Sword of Honour, price=12.99}
["Nigel Rees","Evelyn Waugh"]
Evelyn Waugh
4. 使用谓词过滤项

JsonPath 支持使用过滤器将从 JSON 文档中提取的节点限制为符合谓词(布尔表达式)指定条件的节点。可以使用内联谓词、过滤谓词或自定义谓词。

4.1 内联谓词

内联谓词是基于字符串的谓词。以下是一个示例代码:

import java.util.List;
import com.jayway.jsonpath.JsonPath;

public class JsonPathDemo {
    public static void main(String[] args) {
        String 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\": \"J. R. R. Tolkien\"," +
                        "            \"title\": \"The Lord of the Rings\"," +
                        "            \"isbn\": \"0-395-19395-8\"," +
                        "            \"price\": 22.99" +
                        "         }" +
                        "      ]," +
                        "      \"bicycle\":" +
                        "      {" +
                        "         \"color\": \"red\"," +
                        "         \"price\": 19.95" +
                        "      }" +
                        "   }" +
                        "}";
        String expr = "$.store.book[?(@.isbn)].title";
        List<Object> titles = JsonPath.read(json, expr);
        System.out.println(titles);

        expr = "$.store.book[?(@.category == 'fiction')].title";
        titles = JsonPath.read(json, expr);
        System.out.println(titles);

        expr = "$..book[?(@.author =~ /.*REES/i)].title";
        titles = JsonPath.read(json, expr);
        System.out.println(titles);

        expr = "$..book[?(@.price > 10 && @.price < 20)].title";
        titles = JsonPath.read(json, expr);
        System.out.println(titles);

        expr = "$..book[?(@.author in ['Nigel Rees'])].title";
        titles = JsonPath.read(json, expr);
        System.out.println(titles);

        expr = "$..book[?(@.author nin ['Nigel Rees'])].title";
        titles = JsonPath.read(json, expr);
        System.out.println(titles);
    }
}

以下是代码中使用的 JsonPath 表达式及其作用:
| 表达式 | 说明 |
| — | — |
| $.store.book[?(@.isbn)].title | 返回所有包含 isbn 属性的 book 元素的 title 值 |
| $.store.book[?(@.category == 'fiction')].title | 返回所有 category 属性值为 fiction book 元素的 title 值 |
| $..book[?(@.author =~ /.*REES/i)].title | 返回所有 author 属性值以 rees 结尾(不区分大小写)的 book 元素的 title 值 |
| $..book[?(@.price > 10 && @.price < 20)].title | 返回所有 price 属性值在 10 到 20 之间的 book 元素的 title 值 |
| $..book[?(@.author in ['Nigel Rees'])].title | 返回所有 author 属性值为 Nigel Rees book 元素的 title 值 |
| $..book[?(@.author nin ['Nigel Rees'])].title | 返回所有 author 属性值不为 Nigel Rees book 元素的 title 值 |

编译和运行上述代码后,预期输出如下:

["The Lord of the Rings"]
["Sword of Honour","The Lord of the Rings"]
["Sayings of the Century"]
["Sword of Honour"]
["Sayings of the Century"]
["Sword of Honour","The Lord of the Rings"]
4.2 过滤谓词

过滤谓词是表示为 Filter 抽象类实例的谓词, Filter 类实现了 Predicate 接口。以下是创建和使用过滤谓词的示例代码:

import java.util.List;
import com.jayway.jsonpath.Criteria;
import com.jayway.jsonpath.Filter;
import com.jayway.jsonpath.JsonPath;

public class JsonPathDemo {
    public static void main(String[] args) {
        String 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\": \"J. R. R. Tolkien\"," +
                        "            \"title\": \"The Lord of the Rings\"," +
                        "            \"isbn\": \"0-395-19395-8\"," +
                        "            \"price\": 22.99" +
                        "         }" +
                        "      ]," +
                        "      \"bicycle\":" +
                        "      {" +
                        "         \"color\": \"red\"," +
                        "         \"price\": 19.95" +
                        "      }" +
                        "   }" +
                        "}";

        Filter filter = Filter.filter(Criteria.where("price").lt(20.00));
        String expr = "$['store']['book'][?].title";
        List<Object> titles = JsonPath.read(json, expr, filter);
        System.out.println(titles);
    }
}

创建和使用过滤谓词的步骤如下:
1. 调用 Criteria.where(String key) 静态方法返回一个存储提供的 key Criteria 对象。
2. 调用 Criteria.lt(Object o) 静态方法返回一个表示 < 运算符的 Criteria 对象,该运算符用于标识与 key 值进行比较的值。
3. 将 Criteria 对象传递给 Filter.filter(Predicate predicate) 方法创建 Filter 对象。
4. 在路径中插入 ? 占位符表示过滤谓词。
5. 将过滤谓词传递给 read() 方法。

编译和运行上述代码后,预期输出如下:

["Sayings of the Century","Sword of Honour"]
4.3 自定义谓词

自定义谓词是由实现 Predicate 接口的类创建的谓词。以下是创建和使用自定义谓词的示例代码:

import java.util.List;
import java.util.Map;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Predicate;

public class JsonPathDemo {
    public static void main(String[] args) {
        String 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\": \"J. R. R. Tolkien\"," +
                        "            \"title\": \"The Lord of the Rings\"," +
                        "            \"isbn\": \"0-395-19395-8\"," +
                        "            \"price\": 22.99" +
                        "         }" +
                        "      ]," +
                        "      \"bicycle\":" +
                        "      {" +
                        "         \"color\": \"red\"," +
                        "         \"price\": 19.95" +
                        "      }" +
                        "   }" +
                        "}";
        Predicate expensiveBooks =
                new Predicate() {
                    @Override
                    public boolean apply(Predicate.PredicateContext ctx) {
                        String value = ctx.item(Map.class).get("price").toString();
                        return Float.valueOf(value) > 20.00;
                    }
                };
        String expr = "$.store.book[?]";
        List<Map<String, Object>> titles = JsonPath.read(json, expr, expensiveBooks);
        System.out.println(titles);
    }
}

创建和使用自定义谓词的步骤如下:
1. 实例化一个实现 Predicate 接口的类,并重写 boolean apply(Predicate.PredicateContext ctx) 方法。
2. 在 apply() 方法中,根据 PredicateContext 提供的信息判断当前项是否符合条件,返回 true false
3. 在路径中插入 ? 占位符表示自定义谓词。
4. 将自定义谓词传递给 read() 方法。

编译和运行上述代码后,预期输出如下:

[{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}]
5. 总结

JsonPath 是一种用于选择和提取 JSON 文档属性值的声明性查询语言,类似于 XPath。可以从中央 Maven 仓库获取 JsonPath,也可以手动下载相关 Jar 文件。主要与 com.jayway.jsonpath 包及其类型进行交互,通过编译和使用 JsonPath 表达式可以从 JSON 对象中提取值,还可以使用谓词过滤项。

6. 练习

以下是一些测试对 JsonPath 理解的练习:
1. 定义 JsonPath。
2. 判断:JsonPath 基于 XPath 2.0 是否正确。
3. 指出表示根 JSON 对象的运算符。
4. 说明可以用哪些符号指定 JsonPath 表达式。
5. 指出表示当前正在由过滤谓词处理的节点的运算符。
6. 判断:JsonPath 的深度扫描运算符( .. )是否等同于 XPath 的 / 符号。
7. 说明 JsonPath.compile(String jsonPath, Predicate... filters) 静态方法的作用。
8. 指出 <T> T read(String json) 泛型方法返回 JSON 对象属性名及其值时的返回类型。
9. 识别三种谓词类别。
10. 给定 JSON 对象 { "number": [10, 20, 25, 30] } ,编写一个 JsonPathDemo 应用程序,提取并输出最大值(30)、最小值(10)和平均值(21.25)。

通过这些练习,可以进一步巩固对 JsonPath 的理解和应用能力。

使用 JsonPath 提取 JSON 值

7. 练习答案解析

以下是对前面练习的详细解答:
1. 定义 JsonPath :JsonPath 是一种声明性查询语言(也称为路径表达式语法),用于选择和提取 JSON 文档的属性值。它是一种简单的语言,具有各种类似于 XPath 的功能,用于构造路径表达式,每个表达式以 $ 运算符开头,该运算符标识查询的根元素,对应于 XPath 的 / 符号。
2. 判断:JsonPath 基于 XPath 2.0 是否正确 :答案为错误。JsonPath 虽然有一些功能与 XPath 类似,但它并非基于 XPath 2.0。
3. 指出表示根 JSON 对象的运算符 :表示根 JSON 对象的运算符是 $
4. 说明可以用哪些符号指定 JsonPath 表达式 :可以使用 $ 表示根元素, [ ] 用于访问数组元素, . 用于访问对象属性, * 作为通配符, .. 作为深度扫描运算符等。
5. 指出表示当前正在由过滤谓词处理的节点的运算符 :表示当前正在由过滤谓词处理的节点的运算符是 @
6. 判断:JsonPath 的深度扫描运算符( .. )是否等同于 XPath 的 / 符号 :答案为错误。JsonPath 的深度扫描运算符 .. 用于递归搜索整个 JSON 文档,而 XPath 的 / 符号用于选择根节点或子节点,二者功能不同。
7. 说明 JsonPath.compile(String jsonPath, Predicate... filters) 静态方法的作用 :该方法用于编译一个 JsonPath 表达式,以提高性能,并将编译结果作为 JsonPath 对象返回。 Predicate 可变参数列表允许你指定一个过滤谓词数组,以响应 jsonPath 字符串中的过滤谓词占位符(标识为 ? 字符)。
8. 指出 <T> T read(String json) 泛型方法返回 JSON 对象属性名及其值时的返回类型 :该方法可以返回多种类型,当返回 JSON 对象属性名及其值时,通常返回 java.util.LinkedHashMap 类的实例( java.util.Hashmap 的子类)。
9. 识别三种谓词类别 :三种谓词类别分别是内联谓词、过滤谓词和自定义谓词。
10. 给定 JSON 对象 { "number": [10, 20, 25, 30] } ,编写一个 JsonPathDemo 应用程序,提取并输出最大值(30)、最小值(10)和平均值(21.25)

import java.util.List;
import com.jayway.jsonpath.JsonPath;

public class JsonPathDemo {
    public static void main(String[] args) {
        String json = "{ \"number\": [10, 20, 25, 30] }";

        // 提取最大值
        List<Integer> numbers = JsonPath.read(json, "$.number");
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        int sum = 0;

        for (int num : numbers) {
            if (num > max) {
                max = num;
            }
            if (num < min) {
                min = num;
            }
            sum += num;
        }

        double average = (double) sum / numbers.size();

        System.out.println("最大值: " + max);
        System.out.println("最小值: " + min);
        System.out.println("平均值: " + average);
    }
}
8. 实际应用场景分析

JsonPath 在许多实际场景中都有广泛的应用,以下是一些常见的场景及使用示例:
| 应用场景 | 描述 | 示例代码 |
| — | — | — |
| API 测试 | 在测试 RESTful API 时,需要从返回的 JSON 响应中提取特定的值进行验证。 | java String jsonResponse = "{ \"status\": \"success\", \"data\": { \"id\": 123, \"name\": \"John\" } }"; String status = JsonPath.read(jsonResponse, "$.status"); System.out.println("API 状态: " + status); |
| 数据处理 | 处理大量 JSON 数据时,需要提取特定的字段进行分析或转换。 | java String jsonData = "{ \"users\": [ { \"name\": \"Alice\", \"age\": 25 }, { \"name\": \"Bob\", \"age\": 30 } ] }"; List<String> names = JsonPath.read(jsonData, "$.users[*].name"); System.out.println("用户姓名: " + names); |
| 配置文件解析 | 从 JSON 配置文件中提取配置信息。 | java String jsonConfig = "{ \"database\": { \"host\": \"localhost\", \"port\": 3306 } }"; String host = JsonPath.read(jsonConfig, "$.database.host"); int port = JsonPath.read(jsonConfig, "$.database.port"); System.out.println("数据库主机: " + host); System.out.println("数据库端口: " + port); |

9. 性能优化建议

在使用 JsonPath 时,为了提高性能,可以考虑以下几点建议:
- 编译表达式 :如果需要多次使用相同的 JsonPath 表达式,建议使用 JsonPath.compile() 方法进行编译,避免重复解析表达式。例如:

JsonPath path = JsonPath.compile("$.store.book[1]");
for (int i = 0; i < 100; i++) {
    HashMap books = path.read(json);
    // 处理结果
}
  • 减少不必要的扫描 :尽量使用精确的路径表达式,避免使用深度扫描运算符 .. 进行不必要的全局搜索。
  • 合理使用谓词 :在使用谓词过滤时,确保谓词逻辑简洁高效,避免复杂的条件判断。
10. 总结与展望

JsonPath 作为一种强大的 JSON 查询语言,为处理和操作 JSON 数据提供了便捷的方式。通过使用 JsonPath,可以轻松地从 JSON 对象中提取所需的值,并使用谓词进行灵活的过滤。在实际应用中,要根据具体的场景选择合适的使用方式,并注意性能优化。

未来,随着 JSON 在各种领域的广泛应用,JsonPath 可能会不断发展和完善,支持更多的功能和更复杂的查询需求。同时,与其他技术的集成也将更加紧密,为开发者提供更高效的开发体验。

以下是使用 JsonPath 的基本流程图:

graph TD;
    A[获取 JSON 数据] --> B[编译 JsonPath 表达式];
    B --> C[应用表达式提取值];
    C --> D[使用谓词过滤结果];
    D --> E[处理提取结果];

通过学习和掌握 JsonPath,开发者可以更加高效地处理 JSON 数据,提升开发效率和质量。希望本文能帮助你更好地理解和应用 JsonPath。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值