本文学习了使用 JsonPath 语法从 Json 数据中查找所需元素和数据的基本用法。有一进一,积少成多!

有一进一,积少成多。


引子

JsonPath 是一种面向 JSON 结构的查询语言,正如 SQL 是面向固定 Schema 的表数据的关联查询语言一样。两者都是比较通用的 DSL 。

为什么要学习 JsonPath 呢?

面向语义的编程

大多数程序员都知道主流的编程范式:OBP(机器或汇编编程)、OPP(过程式编程)、OOP(对象式编程)、OFP(函数式编程),实际上还有 OLP(逻辑式编程)、ODP(声明式编程)、OMP(元编程),甚至还可以有 OMP(面向赚钱的编程),OCP(面向意念的编程)(开玩笑的)。

实际上,这些范式归根结底,都是面向语义的编程。二进制、过程、对象、函数、逻辑、元数据等都是一种语义,只是在语义的层次上有高低之分。

何为面向语义的编程?首先确定基本的语法结构和表达语言,然后实现这一套语法和语言,从而能够在这种语言的基础上进行编程。高级语言编程,实际上就是发明了一套理解和使用起来更容易的语法和语言,然后基于这种语言编程。在更灵活的层次上,确定语义,设计接口,然后实现接口。“基于接口编程”是面向语义编程在设计层面的实践。

DSL 也类似。 DSL 是针对特定问题领域而发明的语言。它舍弃了通用编程语言的部分灵活性,致力于在特定领域内实现更为简洁而适配的语法和语言。多掌握几种 DSL ,是有助于解决各种问题的。

JsonPath基本语法

  • $.xxx.yyy.zzz : 获取 JSON 子元素的值;
  • $.array[num] , $.array[start:end], $.array[*], $..array[*] :获取指定数组的值;
  • $.array[*].xyz, $.array[start:end].xyz, $.array[num].xyz : 获取指定数组元素的某个子元素的值;
  • $.array[?(@.xyz = | != | in | =~ | > | < | nin | anyof | noneof value)] : 满足指定条件的数组元素,用于过滤数组元素。

预备数据

{
    "error": 0,
    "status": "success",
    "date": "2014-05-10",
    "extra": {
        "rain": 3,
        "sunny": 2
    },
    "recorder": {
        "name": "qin",
        "time": "2014-05-10 22:00",
        "mood": "good",
        "address": {
            "provice": "ZJ",
            "city": "nanjing"
        }
    },
    "results": [
        {
            "currentCity": "南京",
            "weather_data": [
                {
                    "date": "周六今天,实时19",
                    "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/dayu.png",
                    "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/dayu.png",
                    "weather": "大雨",
                    "wind": "东南风5-6级",
                    "temperature": "18"
                },
                {
                    "date": "周日",
                    "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/zhenyu.png",
                    "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png",
                    "weather": "阵雨转多云",
                    "wind": "西北风4-5级",
                    "temperature": "21~14"
                }
            ]
        }
    ]
}

使用示例


/**
 * jsonpath 用法学习
 *
 * 注意:这个不是单测,只是学习示例,单测必须用断言
 */
public class JsonPathUtilTest2 {

    String json = "{\"error\":0,\"status\":\"success\",\"date\":\"2014-05-10\",\"extra\":{\"rain\":3,\"sunny\":2},\"recorder\":{\"name\":\"qin\",\"time\":\"2014-05-10 22:00\",\"mood\":\"good\",\"address\":{\"provice\":\"ZJ\",\"city\":\"nanjing\"}},\"results\":[{\"currentCity\":\"南京\",\"weather_data\":[{\"date\":\"周六今天,实时19\",\"dayPictureUrl\":\"http://api.map.baidu.com/images/weather/day/dayu.png\",\"nightPictureUrl\":\"http://api.map.baidu.com/images/weather/night/dayu.png\",\"weather\":\"大雨\",\"wind\":\"东南风5-6级\",\"temperature\":\"18\"},{\"date\":\"周日\",\"dayPictureUrl\":\"http://api.map.baidu.com/images/weather/day/zhenyu.png\",\"nightPictureUrl\":\"http://api.map.baidu.com/images/weather/night/duoyun.png\",\"weather\":\"阵雨转多云\",\"wind\":\"西北风4-5级\",\"temperature\":\"21~14\"}]}]}";

    @Test
    public void testBasic() {
        test("$.error");   // 取 error 的值
        test("$.status");  // 取 status 的值
        test("$.date");    // 取 date 的值
    }

    @Test
    public void testBasicNested() {
        test("$.recorder.name");   // 取 recorder.name 的值
        test("$.recorder.mood");   // 取 recorder.mood 的值
        test("$.recorder.address.city");  // 取 recorder.address.city 的值
    }

    @Test
    public void testList() {
        test("$.results");        // 取 results 数组的值
        test("$.results[0].weather_data");      // 取 results 数组的第一个元素的 weather_data 数组的值
        test("$..weather_data.length()");       // 取 results 数组的所有元素的 weather_data 数组的长度(是数组)
        test("$.results[0].weather_data[1]");   // 取 results 数组的第一个元素的 weather_data[1] 的值
        test("$.results[0].weather_data[:1].weather");  // 取 results 数组的第一个元素的 weather_data[0].weather 的值
        test("$.results[0].weather_data[1].weather");   // 取 results 数组的第一个元素的 weather_data[1].weather 的值
        test("$..weather_data[1].weather");             // 取 results 数组的所有元素的含有 weather_data 子元素的 weather 的值(是数组)
        test("$.results[0].weather_data[1].temperature"); // 取 results 数组的第一个元素的 weather_data[1].temperature 的值
        test("$.results[0].weather_data[*].weather");     // 取 results 数组的第一个元素的 weather_data 数组所有元素的 weather 的值(是数组)
    }

    @Test
    public void testListFilter() {

        test("$.results[*].weather_data[?(@.date == '周日')]"); // 取 results 数组的所有元素的 weather_data 数组中 date 与指定值相等的 weather_data 元素(是数组)
        test("$.results[*].weather_data[?(@.weather in ['大雨'])]"); // 取 results 数组的所有元素的 weather_data 数组中 weather 在指定列表的 weather_data 元素(是数组)

        test("$.results[*].weather_data[?(@.temperature =~ /\\d+/i)]"); // 取 results 数组的所有元素的 weather_data 数组中满足 temperature 值为纯数字的 weather_data 元素(是数组)
        test("$.results[*].weather_data[?(@.temperature =~ /\\d+~\\d+/i)]"); // 取 results 数组的所有元素的 weather_data 数组中满足 temperature 值模式匹配 纯数字~纯数字 的 weather_data 元素(是数组)
        test("$.results[*].weather_data[?(@.temperature =~ /\\d+/i)].dayPictureUrl"); // 取 results 数组的所有元素的 weather_data 数组中满足 temperature 值为纯数字的 weather_data 元素的 dayPictureUrl 的值(是数组)
    }


    public void test(String jsonpath) {
        String value = JsonPathUtil.readValByJsonPath(json, jsonpath);
        System.out.println(jsonpath + " => " + value);
        System.out.println();
    }
}

输出:


15:11:05.895 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['recorder']['name']
$.recorder.name => qin

15:11:05.907 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['recorder']['mood']
$.recorder.mood => good

15:11:05.907 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['recorder']['address']['city']
$.recorder.address.city => nanjing

15:11:05.909 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['error']
$.error => 0

15:11:05.909 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['status']
$.status => success

15:11:05.911 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['date']
$.date => 2014-05-10

15:11:05.912 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results']
$.results => [{"currentCity":"南京","weather_data":[{"date":"周六今天,实时19","dayPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/day\/dayu.png","nightPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/night\/dayu.png","weather":"大雨","wind":"东南风5-6级","temperature":"18"},{"date":"周日","dayPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/day\/zhenyu.png","nightPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/night\/duoyun.png","weather":"阵雨转多云","wind":"西北风4-5级","temperature":"21~14"}]}]

15:11:05.918 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][0]['weather_data']
$.results[0].weather_data => [{"date":"周六今天,实时19","dayPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/day\/dayu.png","nightPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/night\/dayu.png","weather":"大雨","wind":"东南风5-6级","temperature":"18"},{"date":"周日","dayPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/day\/zhenyu.png","nightPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/night\/duoyun.png","weather":"阵雨转多云","wind":"西北风4-5级","temperature":"21~14"}]

15:11:05.921 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $..['weather_data'].length()
$..weather_data.length() => [2]

15:11:05.934 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][0]['weather_data'][1]
$.results[0].weather_data[1] => {date=周日, dayPictureUrl=http://api.map.baidu.com/images/weather/day/zhenyu.png, nightPictureUrl=http://api.map.baidu.com/images/weather/night/duoyun.png, weather=阵雨转多云, wind=西北风4-5级, temperature=21~14}

15:11:05.937 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][0]['weather_data'][:1]['weather']
15:11:05.938 [main] DEBUG com.jayway.jsonpath.internal.path.ArrayPathToken - Slice to index on array with length: 2. From index: 0 to: 1. Input: [:1]['weather']
$.results[0].weather_data[:1].weather => ["大雨"]

15:11:05.938 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][0]['weather_data'][1]['weather']
$.results[0].weather_data[1].weather => 阵雨转多云

15:11:05.939 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $..['weather_data'][1]['weather']
$..weather_data[1].weather => ["阵雨转多云"]

15:11:05.940 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][0]['weather_data'][1]['temperature']
$.results[0].weather_data[1].temperature => 21~14

15:11:05.941 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][0]['weather_data'][*]['weather']
$.results[0].weather_data[*].weather => ["大雨","阵雨转多云"]

15:11:05.968 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][*]['weather_data'][?]
15:11:05.969 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['date']
15:11:05.988 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['date']
$.results[*].weather_data[?(@.date == '周日')] => [{"date":"周日","dayPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/day\/zhenyu.png","nightPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/night\/duoyun.png","weather":"阵雨转多云","wind":"西北风4-5级","temperature":"21~14"}]

15:11:05.990 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][*]['weather_data'][?]
15:11:05.990 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['weather']
15:11:05.990 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['weather']
$.results[*].weather_data[?(@.weather in ['大雨'])] => [{"date":"周六今天,实时19","dayPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/day\/dayu.png","nightPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/night\/dayu.png","weather":"大雨","wind":"东南风5-6级","temperature":"18"}]

15:11:05.991 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][*]['weather_data'][?]
15:11:05.991 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['temperature']
15:11:05.992 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['temperature']
$.results[*].weather_data[?(@.temperature =~ /\d+/i)] => [{"date":"周六今天,实时19","dayPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/day\/dayu.png","nightPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/night\/dayu.png","weather":"大雨","wind":"东南风5-6级","temperature":"18"}]

15:11:05.992 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][*]['weather_data'][?]
15:11:05.992 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['temperature']
15:11:05.993 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['temperature']
$.results[*].weather_data[?(@.temperature =~ /\d+~\d+/i)] => [{"date":"周日","dayPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/day\/zhenyu.png","nightPictureUrl":"http:\/\/api.map.baidu.com\/images\/weather\/night\/duoyun.png","weather":"阵雨转多云","wind":"西北风4-5级","temperature":"21~14"}]

15:11:05.994 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $['results'][*]['weather_data'][?]['dayPictureUrl']
15:11:05.994 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['temperature']
15:11:05.994 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @['temperature']
$.results[*].weather_data[?(@.temperature =~ /\d+/i)].dayPictureUrl => ["http:\/\/api.map.baidu.com\/images\/weather\/day\/dayu.png"]

基本实现

public static String readValByJsonPath(String json, String path) {
    if (json == null || path == null) {
        return null;
    }
    try {
        Object val = JsonPath.read(json, path);
        return val == null ? null : val.toString();
    } catch (Exception ex) {
        return null;
    }
}

JAR 依赖:

<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>2.4.0</version>
</dependency> 

JsonPath 可以通过预编译来提升性能。见 “JsonPath:从多层嵌套Json中解析所需要的值”

也有人说,可以用 Snack3 库来实现。当然可以。本文重点是 JsonPath 语法,而具体库的选用则根据实际所需。

小结

本文学习了使用 JsonPath 语法从 Json 数据中查找所需元素和数据的基本用法。有一进一,积少成多!

参考资料