基于json解析神器 jsonpath的使用說明
如果項目需求是從某些復(fù)雜的json里面取值進行計算,用jsonpath+IK(ik-expression)來處理十分方便,jsonpath用來取json里面的值然后用IK自帶的函數(shù)進行計算,如果是特殊的計算那就自定義IK方法搞定,配置化很方便.
下面簡單介紹下jsonpath的使用方法,主要測試都在JsonPathDemo類里面:
下面是一個簡單的java項目demo:

注意: 其中他的max,min,avg,stddev函數(shù)只能類似于如下處理:
//正確寫法 但是感覺很雞肋
context.read("$.avg($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)");
不能傳入list 感覺比較雞肋,如果傳入list 他會報錯(如下錯誤寫法):
//這樣會報錯
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 這樣才被認(rèn)為是數(shù)字
報錯信息都一樣, 如下:
Exception in thread "main" com.jayway.jsonpath.JsonPathException: Aggregation function attempted to calculate value using empty array
JsonPathDemo是一個測試demo:
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 返回所有數(shù)組的值
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 返回下標(biāo)為0 和 2 的數(shù)組值
List<String> name0and2 = context.read("$.result.records[0,2].name");
//["張三","王五"]
System.out.println(name0and2);
//5 返回下標(biāo)為0 到 下標(biāo)為1的 的數(shù)組值 這里[0:2] 表示包含0 但是 不包含2
List<String> name0to2 = context.read("$.result.records[0:2].name");
//["張三","李四"]
System.out.println(name0to2);
//6 返回數(shù)組的最后兩個值
List<String> lastTwoName = context.read("$.result.records[-2:].name");
//["李四","王五"]
System.out.println(lastTwoName);
//7 返回下標(biāo)為1之后的所有數(shù)組值 包含下標(biāo)為1的
List<String> nameFromOne = context.read("$.result.records[1:].name");
//["李四","王五"]
System.out.println(nameFromOne);
//8 返回下標(biāo)為3之前的所有數(shù)組值 不包含下標(biāo)為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屬性的數(shù)組
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屬性的數(shù)組
List<String> list = context.read("$..all");
//["1","4","2","3"]
System.out.println(list);
//12 以當(dāng)前json的某個值為條件查詢 這里ok為1 取出records數(shù)組中applied_at等于1的數(shù)組
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 查詢數(shù)組長度
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 標(biāo)準(zhǔn)差
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文件引入:
<dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>2.3.0</version> </dependency>
其中demo.json是一個測試json:
{
"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:
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為文件結(jié)束
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接口的時候,經(jīng)常要解析JSON,那么可以使用開源jsonpath進行,其中看網(wǎng)上看到相關(guān)的說法不錯的使用場景為:
1、接口關(guān)聯(lián)
也稱為關(guān)聯(lián)參數(shù)。在應(yīng)用業(yè)務(wù)接口中,完成一個業(yè)務(wù)功能時,有時候一個接口可能不滿足業(yè)務(wù)的整個流程邏輯,需要多個接口配合使用,簡單的案例如:B接口的成功調(diào)用依賴于A接口,需要在A接口的響應(yīng)數(shù)據(jù)(response)中拿到需要的字段,在調(diào)用B接口的時候,傳遞給B接口作為B接口請求參數(shù),拿到后續(xù)響應(yīng)的響應(yīng)數(shù)據(jù)。
接口關(guān)聯(lián)通??梢允褂谜齽t表達式去提取需要的數(shù)據(jù),但對于json這種簡潔、清晰層次結(jié)構(gòu)、輕量級的數(shù)據(jù)交互格式,使用正則未免有點殺雞用牛刀的感覺(是的,因為我不擅長寫正則表達式),我們需要更加簡單、直接的提取json數(shù)據(jù)的方式。
2、數(shù)據(jù)驗證
這里的數(shù)據(jù)驗證指的是對響應(yīng)結(jié)果進行數(shù)據(jù)的校驗
接口自動化測試中,對于簡單的響應(yīng)結(jié)果(json),可以直接和期望結(jié)果進行比對,判斷是否完全相等即可。
如 json {"status":1,"msg":"登錄成功"}
3、對于格式較復(fù)雜
尤其部分?jǐn)?shù)據(jù)存在不確定性、會根據(jù)實際情況變化的響應(yīng)結(jié)果,簡單的判斷是否完全相等(斷言)通常會失敗。
如:
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結(jié)構(gòu)嵌套了很多信息,完整的匹配幾乎不可能成功。比如其中的createTime信息,根據(jù)執(zhí)行接口測試用例的時間每次都不一樣。同時這個時間是響應(yīng)結(jié)果中較為次要的信息,在進行接口自動化測試時,是可以選擇被忽略的。
4、我們需要某種簡單的方法
能夠從json中提取出我們真正關(guān)注的信息(通常也被稱為關(guān)鍵信息)。
如提取出status的值為1,data數(shù)組中每個對象的investId都為1,data中第三個對象的unfinishedPrincipal值為100.00,只要這三個關(guān)鍵信息校驗通過,我們就認(rèn)為響應(yīng)結(jié)果沒有問題。
JSONPATH有點像XPATH了,語法規(guī)則小結(jié)下:
這里有個表格,說明JSONPath語法元素和對應(yīng)XPath元素的對比。
| XPath | JSONPath | Description |
| / | $ | 表示根元素 |
| . | @ | 當(dāng)前元素 |
| / | . or [] | 子元素 |
| .. | n/a | 父元素 |
| // | .. | 遞歸下降,JSONPath是從E4X借鑒的。 |
| * | * | 通配符,表示所有的元素 |
| @ | n/a | 屬性訪問字符 |
| [] | [] |
子元素操作符 |
| | | [,] |
連接操作符在XPath 結(jié)果合并其它結(jié)點集合。JSONP允許name或者數(shù)組索引。 |
| n/a | [start:end:step] |
數(shù)組分割操作從ES4借鑒。 |
| [] | ?() |
應(yīng)用過濾表示式 |
| n/a | () |
腳本表達式,使用在腳本引擎下面。 |
| () | n/a | Xpath分組 |
下面是一個簡單的json數(shù)據(jù)結(jié)構(gòu)代表一個書店(原始的xml文件是)
{ "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 | 結(jié)果 |
| /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中,就可以這樣使用:
@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));
}
}
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Java中多線程與并發(fā)_volatile關(guān)鍵字的深入理解
這篇文章主要給大家介紹了關(guān)于Java中多線程與并發(fā)_volatile關(guān)鍵字的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
java swing實現(xiàn)的掃雷游戲及改進版完整示例
這篇文章主要介紹了java swing實現(xiàn)的掃雷游戲及改進版,結(jié)合完整實例形式對比分析了java使用swing框架實現(xiàn)掃雷游戲功能與相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
SpringBoot中ApplicationEvent和ApplicationListener用法小結(jié)
這篇文章介紹SpringBoot中ApplicationEvent用法,注意ApplicationEvent和MQ隊列雖然實現(xiàn)的功能相似,但是MQ還是有其不可替代性的,最本質(zhì)的區(qū)別就是MQ可以用于不同系統(tǒng)之間的消息發(fā)布,而SpringEvent這種模式只能在一個系統(tǒng)中,需要的朋友可以參考下2023-03-03
一文探究ArrayBlockQueue函數(shù)及應(yīng)用場景
這篇文章主要為大家介紹了一文探究ArrayBlockQueue函數(shù)及應(yīng)用場景,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03

