JsonPath简明教程
有一进一,积少成多。
引子
JsonPath 是一种面向 JSON 结构的查询语言,正如 SQL 是面向固定 Schema 的表数据的关联查询语言一样。两者都是比较通用的 DSL 。
为什么要学习 JsonPath 呢?
面向语义的编程
大多数程序员都知道主流的编程范式:BP(机器或汇编编程)、PP(过程式编程)、OP(对象式编程)、FP(函数式编程),实际上还有 LP(逻辑式编程)、DP(声明式编程)、MP(元编程)、RP(响应式编程),甚至还可以有 MP(面向赚钱的编程),CP(面向意念的编程)。开玩笑的 😃。
实际上,这些范式归根结底,都是面向语义的编程。二进制、过程、对象、函数、逻辑、元数据等都是一种语义,只是在语义的层次上有高低之分。
何为面向语义的编程?首先确定基本的语法结构和表达语言,然后实现这一套语法和语言,从而能够在这种语言的基础上进行编程。高级语言编程,实际上就是发明了一套理解和使用起来更容易的语法和语言,然后基于这种语言编程。在更灵活的层次上,确定语义,设计接口,然后实现接口。“基于接口编程”是面向语义编程在设计层面的实践。
DSL 也类似。 DSL 是针对特定问题领域而发明的语言。它舍弃了通用编程语言的部分灵活性,致力于在特定领域内实现更为简洁而适配的语法和语言。多掌握几种 DSL ,是有助于解决各种问题的。
JsonPath基本语法
- $.xxx.yyy.zzz : 获取 JSON 子元素的值;
- $.array[*], $..array[*] :获取指定数组的值;
- $.array[num] , $.array[start:end] :获取指定数组元素的值;
- $.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 数据中查找所需元素和数据的基本用法。有一进一,积少成多!