基于json解析神器jsonpath的使⽤说明
如果项⽬需求是从某些复杂的json⾥⾯取值进⾏计算,⽤jsonpath+IK(ik-expression)来处理⼗分⽅便,jsonpath⽤来取json⾥⾯的值然后⽤IK⾃带的函数进⾏计算,如果是特殊的计算那就⾃定义IK⽅法搞定,配
置化很⽅便.
下⾯简单介绍下jsonpath的使⽤⽅法,主要测试都在JsonPathDemo类⾥⾯:
下⾯是⼀个简单的java项⽬demo:
注意: 其中他的max,min,avg,stddev函数只能类似于如下处理:
//正确写法但是感觉很鸡肋
不能传⼊list 感觉⽐较鸡肋,如果传⼊list 他会报错(如下错误写法):
//这样会报错
Object maxV = ad("$.max($.ds[*].loan_type)");
//这样也会报错
Object maxV = ad("$.ds[*].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:
public class JsonPathDemo {
public static void main(String[] args) {
String json = adFileByLines("demo.json");
ReadContext context = JsonPath.parse(json);
//1 返回所有name
List<String> names = ad("$.ds[*].name");
//["张三","李四","王五"]
System.out.println(names);
//2 返回所有数组的值
List<Map<String, String>> objs = ad("$.ds[*]");
/
/[{"name":"张三","pid":*******************","mobile":"186****6789","applied_at":"3","confirmed_at":"5","confirm_type":"overdue","loan_type":"1","test":"mytest","all":"2"},{"name":"李四","pid":"500234199299999999","mobile":"130****5432","applied_at":"1", System.out.println(objs);
//3 返回第⼀个的name
String name0 = ad("$.ds[0].name");
//张三
System.out.println(name0);
//4 返回下标为0 和 2 的数组值
List<String> name0and2 = ad("$.ds[0,2].name");
//["张三","王五"]
System.out.println(name0and2);
//5 返回下标为0 到下标为1的的数组值这⾥[0:2] 表⽰包含0 但是不包含2
List<String> name0to2 = ad("$.ds[0:2].name");
//["张三","李四"]
System.out.println(name0to2);
//6 返回数组的最后两个值
List<String> lastTwoName = ad("$.ds[-2:].name");
//["李四","王五"]
System.out.println(lastTwoName);
//7 返回下标为1之后的所有数组值包含下标为1的
List<String> nameFromOne = ad("$.ds[1:].name");
//["李四","王五"]
System.out.println(nameFromOne);
/
/8 返回下标为3之前的所有数组值不包含下标为3的
List<String> nameEndTwo = ad("$.ds[:3].name");
//["张三","李四","王五"]
System.out.println(nameEndTwo);
//9 返回applied_at⼤于等于2的值
List<Map<String, String>> records = ad("$.ds[?(@.applied_at >= '2')]");
//[{"name":"张三","pid":*******************","mobile":"186****6789","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 = ad("$.ds[?(@.name == '李四')]");
/
/[{"name":"李四","pid":"500234199299999999","mobile":"130****5432","applied_at":"1","confirmed_at":"","confirm_type":"overdue","loan_type":"3"}]
System.out.println(records0);
//11 返回有test属性的数组
List<Map<String, String>> records1 = ad("$.ds[?(@.test)]");
//[{"name":"张三","pid":*******************","mobile":"186****6789","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 = ad("$..all");
//["1","4","2","3"]
System.out.println(list);
//12 以当前json的某个值为条件查询这⾥ok为1 取出records数组中applied_at等于1的数组
List<String> ok = ad("$.ds[?(@.applied_at == $['ok'])]");
//["1","4","2","3"]
System.out.println(ok);
//13 正则匹配
List<String> regexName = ad("$.ds[?(@.pid =~ /.*999/i)]");
//[{"name":"李四","pid":"500234199299999999","mobile":"130****5432","applied_at":"1","confirmed_at":"","confirm_type":"overdue","loan_type":"3","all":"3"}]
System.out.println(regexName);
//14 多条件
List<String> mobile = ad("$.ds[?(@.all == '2' || @.name == '李四' )].mobile");
//["186****6789","130****5432"]
System.out.println(mobile);
//14 查询数组长度
Integer length01 = ad("$.ds.length()");
//3
System.out.println(length01);
//15 查询list⾥⾯每个对象长度
List<Integer> length02 = ad("$.ds[?(@.all == '2' || @.name == '李四' )].length()");
//[9,8]
System.out.println(length02);
//16 最⼤值
Object maxV = ad("$.max($.ds[0].loan_type,$.ds[1].loan_type,$.ds[2].loan_type)");
//3.0
System.out.println(maxV);
//17 最⼩值
Object minV = ad("$.min($.ds[0].loan_type,$.ds[1].loan_type,$.ds[2].loan_type)");
//1.0
System.out.println(minV);
//18 平均值
double avgV = ad("$.avg($.ds[0].loan_type,$.ds[1].loan_type,$.ds[2].loan_type)");
//2.3333333333333335
System.out.println(avgV);
正则匹配一个或连续多个
//19 标准差
double stddevV = ad("$.stddev($.ds[0].loan_type,$.ds[1].loan_type,$.ds[2].loan_type)"); //0.9428090415820636
System.out.println(stddevV);
//20 读取⼀个不存在的
String haha = ad("$.result.haha");
//抛出异常
//Exception in thread "main" com.jayway.jsonpath.PathNotFoundException: No results for path: $['result']['haha']
//at com.jayway.jsonpath.internal.Value(EvaluationContextImpl.java:133)
//at com.jayway.ad(JsonPath.java:187)
//at com.jayway.jsonpath.ad(JsonContext.java:102)
//at com.jayway.jsonpath.ad(JsonContext.java:89)
//at cn.lijie.jsonpath.JsonPathDemo.main(JsonPathDemo.java:58)
//flect.NativeMethodAccessorImpl.invoke0(Native Method)
//flect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
//flect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
//at flect.Method.invoke(Method.java:498)
//at cution.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":*******************",
"mobile":"186****6789",
"applied_at": "3",
"confirmed_at": "5",
"confirm_type": "overdue",
"loan_type": 1,
"test": "mytest",
"all": "2"
},
{
"name": "李四",
"pid": "500234199299999999",
"mobile":"130****5432",
"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 = ClassLoader().getResourceAsStream(fileName);
reader = new BufferedReader(new InputStreamReader(is));
String tempString = null;
int line = 1;
// ⼀次读⼊⼀⾏,直到读⼊null为⽂件结束
while ((tempString = adLine()) != 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、对于格式较复杂
尤其部分数据存在不确定性、会根据实际情况变化的响应结果,简单的判断是否完全相等(断⾔)通常会失败。
如:
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
上⾯的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⽂件是)
{ "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[0,1]
/
/book[position()<3]$..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));
}
}
以上为个⼈经验,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。如有错误或未考虑完全的地⽅,望不吝赐教。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。