[TOC]
JSON对比
JSONassert
基本用法
public void test() throws JSONException {
String expected = "{\"code\":0,\"data\":{\"items\":[1,2]}}";
String actual = "{\"code\":0,\"data\":{\"items\":[1,2]},\"msg\":\"hi\"}";
JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT);
}
JSONCompareMode
总的来说,对比模式就是以下两个检查点的组合:
- JSON 是否可扩展(extensible):
如:预期{"k1":1},实际{"k1":1,"k2":2},新增了k2,视为扩展
- JSON 中的数组是否严格排序(strict array ordering):
如:预期{"k1":[1,2,3]},实际{"k1":[2,3,1]},数组顺序不同,视为未严格排序
模式 | 说明 |
---|---|
严格模式(STRICT) | 不可扩展, 数组严格排序 |
宽大模式(LENIENT) | 可扩展,数组不用排序 |
不可扩展模式(NON_EXTENSIBLE) | 如字面意思,可忽略数组排序 |
严格排序模式(STRICT_ORDER) | 如字面意思,可扩展 |
举例
// 预期
{"code":0,"data":{"items":[1,2]}}
// 严格模式:pass
{"code":0,"data":{"items":[1,2]}}
// 宽大模式:pass
{"code":0,"data":{"items":[1,2]},"msg":"hi"}
{"code":0,"data":{"items":[2,1]}}
// 不可扩展模式:pass
{"code":0,"data":{"items":[2,1]}}
// 严格排序模式:pass
{"code":0,"data":{"items":[1,2]},"msg":"hi"}
自定义模式
举例,我们想忽略一些字段 ,假设是 updateTime{"code":0,"data":{"items":{"name":"nana","updateTime":1590206371049}}}
方法一,使用宽大模式或严格排序模式,把预期结果写成{"code":0,"data":{"items":{"name":"nana"}}}
方法二,在以上 4 种模式的基础上,自定义忽略 updateTime 的校验
// demo 1
public void test() throws JSONException {
String expected = "{\"code\":0,\"data\":{\"item\":{\"name\":\"nana\",\"updateTime\":1590206371049}}}";
String actual = "{\"code\":0,\"data\":{\"item\":{\"name\":\"nana\",\"updateTime\":123}}}";
CustomComparator comparator = new CustomComparator(JSONCompareMode.LENIENT,
new Customization("data.item.updateTime", (o1, o2) -> true));
JSONAssert.assertEquals(expected, actual, comparator);
}
// demo 2
public void testArray() throws JSONException {
String expected = "{\"code\":0,\"data\":{\"items\":[{\"name\":\"nana\",\"updateTime\":1590206371049},{\"name\":\"lili\",\"updateTime\":1590206371050}]}}";
String actual ="{\"code\":0,\"data\":{\"items\":[{\"name\":\"nana\",\"updateTime\":123},{\"name\":\"lili\",\"updateTime\":123}]}}";
CustomComparator comparator = new CustomComparator(JSONCompareMode.LENIENT,
new Customization("data.items[*].updateTime", (o1, o2) -> true));
JSONAssert.assertEquals(expected, actual, comparator);
}
JSON内容提取
fastjson
基本用法
public void testFastjson() {
String actual = "{\"code\":0}";
JSONObject jsonObject = JSONObject.parseObject(actual);
SoftAssert softAssert = new SoftAssert();
// key 不存在时,返回null
softAssert.assertEquals(jsonObject.get("code1"), 0, "fastjson:jsonObject.get");
// key 不存在时,默认返回 0,导致断言依然会通过,不建议使用
softAssert.assertEquals(jsonObject.getIntValue("code1"), 0, "fastjson:getIntValue.getIntValue");
softAssert.assertAll();
}
jackson
基本用法
public void testJackson() throws JsonProcessingException {
String actual = "{\"code\":0}";
JsonMapper mapper = new JsonMapper();
JsonNode jsonNode = mapper.readTree(actual);
SoftAssert softAssert = new SoftAssert();
// jsonNode.get:key 不存在时,抛 NullPointerException 异常
softAssert.assertEquals(jsonNode.get("code1").asInt(), 0, "jackson:jsonNode.get");
// jsonNode.asInt:key 不存在时,默认返回 0,但也可以设置默认值
// 如果要用 asXXX 方法,一定要默认值与预期结果不同
softAssert.assertEquals(jsonNode.at("/code1").asInt(), 0, "jackson:jsonNode.at");
softAssert.assertEquals(jsonNode.at("/code1").asInt(-1), 0, "jsonNode.at");
softAssert.assertAll();
}
JsonPath
基本用法
public void testJsonpath() {
String actual = "{\"code\":0}";
// JsonPath.read:没有 key 时,抛 PathNotFoundException 异常
Assert.assertEquals((int) JsonPath.read(actual, "$.code1"), 0, "JsonPath:JsonPath.read");
}
提取 JOSN 字符串总结
- 推荐使用 JsonPath,通过路径可以灵活地获取数据,支持正则提取、列表提取,传送门
- fastjson 的坑:禁止使用
jsonObject.getXXXValue("key")
, 原因是 key 不存在时,依然有默认值,默认值和预期结果相同时,就暴露不出问题 - jackson 的坑:禁止使用
jsonNode.at("path").asXXX()
,原因与 fastjson 相同。但 jackson 允许手动设置默认值,假设预期结果是 0,则提取时应手动设置非 0 默认值asInt(-1)
JSON Schema
JSON Schema 是验证 JSON 数据结构的强大工具,简单介绍:Understanding JSON Schema
实现
目前,JSON Schema 已发布的最新草案是 2019-09(Draft 8)。各语言目前实现到了Draft 7。参考:Implementations
- Java版本实现推荐:json-schema-validator draft-07, -06, -04 Support OpenAPI 3.0 with Jackson parser (Apache License 2.0) - 已封装
public void testJsonSchema() { String actual = "{\"code\":0}"; String expectedJsonSchema = "{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"number\",\"const\":1}}}"; Checker.assertJsonSchema(expectedJsonSchema, actual); } public static void assertJsonSchema(String schema, String instance, String... messages) { ObjectMapper mapper = new ObjectMapper(); String message = ""; if (messages.length > 0) message = messages[0] + "\n"; try { ObjectNode expected = (ObjectNode) mapper.readTree(schema); JsonNode actual = mapper.readTree(instance); // 使用 draft-07 规则 expected.put("$schema", "http://json-schema.org/draft-07/schema#"); JsonSchemaFactory validatorFactory = JsonSchemaFactory. builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)). objectMapper(mapper). build(); JsonSchema jsonSchema = validatorFactory.getSchema(expected); Set
errors = jsonSchema.validate(actual); Assert.assertTrue(errors.size() == 0, message + errors.toString()); } catch (IOException e) { Assert.fail(message + "字符串转 JSON 失败"); } } - Web (Online)推荐:JSON Schema Validator draft-07, -06, -04, -03