如果項目需求是從某些復雜的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