基于json解析神器 jsonpath的使用说明

如果项目需求是从某些复杂的json里面取值进行计算,用jsonpath+IK(ik-expression)来处理十分方便,jsonpath用来取json里面的值然后用IK自带的函数进行计算,如果是特殊的计算那就自定义IK方法搞定,配置化很方便.

下面简单介绍下jsonpath的使用方法,主要测试都在JsonPathDemo类里面:

下面是一个简单的java项目demo:


注意: 其中他的max,min,avg,stddev函数只能类似于如下处理:

1
2
//正确写法 但是感觉很鸡肋
context.read("$.avg($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)");
不能传入list 感觉比较鸡肋,如果传入list 他会报错(如下错误写法):

1
2
3
4
5
//这样会报错
Object maxV = context.read("$.max($.result.records[*].loan_type)");
//这样也会报错
Object maxV = context.read("$.result.records[*].loan_type.max()");
//如果json文件中是这样:"loan_type":"2",也会报错,"loan_type":2 这样才被认为是数字
报错信息都一样, 如下:

Exception in thread "main" com.jayway.jsonpath.JsonPathException: Aggregation function attempted to calculate value using empty array

JsonPathDemo是一个测试demo:
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
public class JsonPathDemo {
 public static void main(String[] args) {
 String json = FileUtils.readFileByLines("demo.json");
 ReadContext context = JsonPath.parse(json);
 //1 返回所有name
 List<String> names = context.read("$.result.records[*].name");
 //["张三","李四","王五"]
 System.out.println(names);
 //2 返回所有数组的值
 List<Map<String, String>> objs = context.read("$.result.records[*]");
 //[{"name":"张三","pid":"500234199212121212","mobile":"18623456789","applied_at":"3","confirmed_at":"5","confirm_type":"overdue","loan_type":"1","test":"mytest","all":"2"},{"name":"李四","pid":"500234199299999999","mobile":"13098765432","applied_at":"1","confirmed_at":"","confirm_type":"overdue","loan_type":"3","all":"3"},{"name":"王五","pid":"50023415464654659","mobile":"1706454894","applied_at":"-1","confirmed_at":"","confirm_type":"overdue","loan_type":"3"}]
 System.out.println(objs);
 //3 返回第一个的name
 String name0 = context.read("$.result.records[0].name");
 //张三
 System.out.println(name0);
 //4 返回下标为0 和 2 的数组值
 List<String> name0and2 = context.read("$.result.records[0,2].name");
 //["张三","王五"]
 System.out.println(name0and2);
 //5 返回下标为0 到 下标为1的 的数组值 这里[0:2] 表示包含0 但是 不包含2
 List<String> name0to2 = context.read("$.result.records[0:2].name");
 //["张三","李四"]
 System.out.println(name0to2);
 //6 返回数组的最后两个值
 List<String> lastTwoName = context.read("$.result.records[-2:].name");
 //["李四","王五"]
 System.out.println(lastTwoName);
 //7 返回下标为1之后的所有数组值 包含下标为1的
 List<String> nameFromOne = context.read("$.result.records[1:].name");
 //["李四","王五"]
 System.out.println(nameFromOne);
 //8 返回下标为3之前的所有数组值 不包含下标为3的
 List<String> nameEndTwo = context.read("$.result.records[:3].name");
 //["张三","李四","王五"]
 System.out.println(nameEndTwo);
 //9 返回applied_at大于等于2的值
 List<Map<String, String>> records = context.read("$.result.records[?(@.applied_at >= '2')]");
 //[{"name":"张三","pid":"500234199212121212","mobile":"18623456789","applied_at":"3","confirmed_at":"5","confirm_type":"overdue","loan_type":"1","test":"mytest","all":"2"}]
 System.out.println(records);
 //10 返回name等于李四的值
 List<Map<String, String>> records0 = context.read("$.result.records[?(@.name == '李四')]");
 //[{"name":"李四","pid":"500234199299999999","mobile":"13098765432","applied_at":"1","confirmed_at":"","confirm_type":"overdue","loan_type":"3"}]
 System.out.println(records0);
 //11 返回有test属性的数组
 List<Map<String, String>> records1 = context.read("$.result.records[?(@.test)]");
 //[{"name":"张三","pid":"500234199212121212","mobile":"18623456789","applied_at":"3","confirmed_at":"5","confirm_type":"overdue","loan_type":"1","test":"mytest","all":"2"}]
 System.out.println(records1);
 //12 返回有test属性的数组
 List<String> list = context.read("$..all");
 //["1","4","2","3"]
 System.out.println(list);
 //12 以当前json的某个值为条件查询 这里ok为1 取出records数组中applied_at等于1的数组
 List<String> ok = context.read("$.result.records[?(@.applied_at == $['ok'])]");
 //["1","4","2","3"]
 System.out.println(ok);
 //13 正则匹配
 List<String> regexName = context.read("$.result.records[?(@.pid =~ /.*999/i)]");
 //[{"name":"李四","pid":"500234199299999999","mobile":"13098765432","applied_at":"1","confirmed_at":"","confirm_type":"overdue","loan_type":"3","all":"3"}]
 System.out.println(regexName);
 //14 多条件
 List<String> mobile = context.read("$.result.records[?(@.all == '2' || @.name == '李四' )].mobile");
 //["18623456789","13098765432"]
 System.out.println(mobile);
 //14 查询数组长度
 Integer length01 = context.read("$.result.records.length()");
 //3
 System.out.println(length01);
 //15 查询list里面每个对象长度
 List<Integer> length02 = context.read("$.result.records[?(@.all == '2' || @.name == '李四' )].length()");
 //[9,8]
 System.out.println(length02);
 //16 最大值
 Object maxV = context.read("$.max($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)");
 //3.0
 System.out.println(maxV);
 //17 最小值
 Object minV = context.read("$.min($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)");
 //1.0
 System.out.println(minV);
 //18 平均值
 double avgV = context.read("$.avg($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)");
 //2.3333333333333335
 System.out.println(avgV);
 //19 标准差
 double stddevV = context.read("$.stddev($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)");
 //0.9428090415820636
 System.out.println(stddevV);
 //20 读取一个不存在的
 String haha = context.read("$.result.haha");
 //抛出异常
 //Exception in thread "main" com.jayway.jsonpath.PathNotFoundException: No results for path: $['result']['haha']
 //at com.jayway.jsonpath.internal.path.EvaluationContextImpl.getValue(EvaluationContextImpl.java:133)
 //at com.jayway.jsonpath.JsonPath.read(JsonPath.java:187)
 //at com.jayway.jsonpath.internal.JsonContext.read(JsonContext.java:102)
 //at com.jayway.jsonpath.internal.JsonContext.read(JsonContext.java:89)
 //at cn.lijie.jsonpath.JsonPathDemo.main(JsonPathDemo.java:58)
 //at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 //at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 //at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 //at java.lang.reflect.Method.invoke(Method.java:498)
 //at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
 System.out.println(haha);
 }
}
pom文件引入:
1
2
3
4
5
<dependency>
 <groupId>com.jayway.jsonpath</groupId>
 <artifactId>json-path</artifactId>
 <version>2.3.0</version>
</dependency>
其中demo.json是一个测试json:
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
{
 "action": "/interface.service/xxx/queryBlackUserData",
 "all": "1",
 "result": {
 "count": 2,
 "tenant_count": 2,
 "records": [
 {
 "name": "张三",
 "pid": "500234199212121212",
 "mobile": "18623456789",
 "applied_at": "3",
 "confirmed_at": "5",
 "confirm_type": "overdue",
 "loan_type": 1,
 "test": "mytest",
 "all": "2"
 },
 {
 "name": "李四",
 "pid": "500234199299999999",
 "mobile": "13098765432",
 "applied_at": "1",
 "confirmed_at": "",
 "confirm_type": "overdue",
 "loan_type": 3,
 "all": "3"
 },
 {
 "name": "王五",
 "pid": "50023415464654659",
 "mobile": "1706454894",
 "applied_at": "-1",
 "confirmed_at": "",
 "confirm_type": "overdue",
 "loan_type": 3
 }
 ],
 "all": "4"
 },
 "code": 200,
 "subtime": "1480495123550",
 "status": "success",
 "ok": 3
}
FileUtils类是用于读取xx.json文件为字符串的json:
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
public class FileUtils {
 /**
 * 以行为单位读取文件,常用于读面向行的格式化文件
 */
 public static String readFileByLines(String fileName) {
 File file = new File(fileName);
 BufferedReader reader = null;
 String str = "";
 try {
  InputStream is = FileUtils.class.getClassLoader().getResourceAsStream(fileName);
  reader = new BufferedReader(new InputStreamReader(is));
  String tempString = null;
  int line = 1;
  // 一次读入一行,直到读入null为文件结束
  while ((tempString = reader.readLine()) != null) {
  // 显示行号
  str += tempString;
  }
  reader.close();
 } catch (IOException e) {
  e.printStackTrace();
 } finally {
  if (reader != null) {
  try {
   reader.close();
  } catch (IOException e1) {
  }
  }
 }
 return str;
 }
}
补充:json接口测试的利器jsonpath

在测试REST接口的时候,经常要解析JSON,那么可以使用开源jsonpath进行,其中看网上看到相关的说法不错的使用场景为:

1、接口关联
也称为关联参数。在应用业务接口中,完成一个业务功能时,有时候一个接口可能不满足业务的整个流程逻辑,需要多个接口配合使用,简单的案例如:B接口的成功调用依赖于A接口,需要在A接口的响应数据(response)中拿到需要的字段,在调用B接口的时候,传递给B接口作为B接口请求参数,拿到后续响应的响应数据。

接口关联通常可以使用正则表达式去提取需要的数据,但对于json这种简洁、清晰层次结构、轻量级的数据交互格式,使用正则未免有点杀鸡用牛刀的感觉(是的,因为我不擅长写正则表达式),我们需要更加简单、直接的提取json数据的方式。

2、数据验证
这里的数据验证指的是对响应结果进行数据的校验

接口自动化测试中,对于简单的响应结果(json),可以直接和期望结果进行比对,判断是否完全相等即可。

如 json {"status":1,"msg":"登录成功"}

3、对于格式较复杂
尤其部分数据存在不确定性、会根据实际情况变化的响应结果,简单的判断是否完全相等(断言)通常会失败。

如:

1
json {"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"获取信息成功"}
上面的json结构嵌套了很多信息,完整的匹配几乎不可能成功。比如其中的createTime信息,根据执行接口测试用例的时间每次都不一样。同时这个时间是响应结果中较为次要的信息,在进行接口自动化测试时,是可以选择被忽略的。

4、我们需要某种简单的方法
能够从json中提取出我们真正关注的信息(通常也被称为关键信息)。

如提取出status的值为1,data数组中每个对象的investId都为1,data中第三个对象的unfinishedPrincipal值为100.00,只要这三个关键信息校验通过,我们就认为响应结果没有问题。

上一篇:Rsync+Inotify实时同步环境部署


下一篇:c++ vector 的使用