好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

基于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,只要这三个关键信息校验通过,我们就认为响应结果没有问题。

JSONPATH有点像XPATH了,语法规则小结下:

这里有个表格,说明JSONPath语法元素和对应XPath元素的对比。

XPath JSONPath Description
/ $ 表示根元素
. @ 当前元素
/ . or [] 子元素
.. n/a 父元素
// .. 递归下降,JSONPath是从E4X借鉴的。
* * 通配符,表示所有的元素
@ n/a 属性访问字符
[] []

子元素操作符

| [,]

连接操作符在XPath 结果合并其它结点集合。JSONP允许name或者数组索引。

n/a [start:end:step]

数组分割操作从ES4借鉴。

[] ?()

应用过滤表示式

n/a ()

脚本表达式,使用在脚本引擎下面。

() n/a Xpath分组

下面是一个简单的json数据结构代表一个书店(原始的xml文件是)

?

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

{ "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

  }

  }

}

XPath JSONPath 结果
/store/book/author $.store.book[*].author

书点所有书的作者

//author $..author

所有的作者

/store/* $.store.*

store的所有元素。所有的bookst和bicycle

/store//price $.store..price

store里面所有东西的price

//book[3] $..book[2]

第三个书

//book[last()] $..book[(@.length-1)] 最后一本书
//book[position()<3] $..book[0,1]

$..book[:2]

前面的两本书。
//book[isbn] $..book[?(@.isbn)] 过滤出所有的包含isbn的书。
//book[price<10] $..book[?(@.price<10)] 过滤出价格低于10的书。
//* $..*

所有元素。

比如在单元测试MOCK中,就可以这样使用:

?

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

@RunWith (SpringRunner. class )

@SpringBootTest

@AutoConfigureMockMvc

@ActiveProfiles ( "test" )

public class BookControllerTest {

  @Autowired

  private MockMvc mockMvc;

  @MockBean

  private BookRepository mockRepository;

  /*

  {

   "timestamp":"2019-03-05T09:34:13.280+0000",

   "status":400,

   "errors":["Author is not allowed.","Please provide a price","Please provide a author"]

  }

  */

  //article : jsonpath in array

  @Test

  public void save_emptyAuthor_emptyPrice_400() throws Exception {

 

  String bookInJson = "{\"name\":\"ABC\"}" ;

  mockMvc.perform(post( "/books" )

   .content(bookInJson)

   .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))

   .andDo(print())

   .andExpect(status().isBadRequest())

   .andExpect(jsonPath( "$.timestamp" , is(notNullValue())))

   .andExpect(jsonPath( "$.status" , is( 400 )))

   .andExpect(jsonPath( "$.errors" ).isArray())

   .andExpect(jsonPath( "$.errors" , hasSize( 3 )))

   .andExpect(jsonPath( "$.errors" , hasItem( "Author is not allowed." )))

   .andExpect(jsonPath( "$.errors" , hasItem( "Please provide a author" )))

   .andExpect(jsonPath( "$.errors" , hasItem( "Please provide a price" )));

 

  verify(mockRepository, times( 0 )).save(any(Book. class ));

 

  }

}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。

原文链接:https://blog.csdn.net/qq_20641565/article/details/77162868

查看更多关于基于json解析神器 jsonpath的使用说明的详细内容...

  阅读:27次