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

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

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

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

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

//正确写法 但是感觉很鸡肋

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 这样才被认为是数字

报错信息都一样, 如下:

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 返回所有数组的值

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文件引入:

<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为文件结束

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、对于格式较复杂

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

如:

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元素的对比。

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

子元素操作符

|[,]

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

n/a[start:end:step]

数组分割操作从ES4借鉴。

[]?()

应用过滤表示式

n/a()

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

()n/aXpath分组

下面是一个简单的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

}

}

}

XPathJSONPath结果
/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));

}

}

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

以上是 基于json解析神器 jsonpath的使用说明 的全部内容, 来源链接: utcz.com/z/311554.html

回到顶部