根据实际需求,发现前后端都需要对数据进行校验,且校验字段较多。为避免在代码中加入繁琐的判断,且能前后端保持统一标准,我们使用JSON schema规范来进行数据的校验。
json-schema 中文意思是 JSON的模式。定义一个JSON的规范,对数据的格式进行约束。比如数据的类型,数字number,字符串 string, 对象Object;数字的大小和区间范围;是否要求必填等等;
如下是一个校验字段fieldA,fieldB,fieldC的样例。我们要求fieldA是字符串,且值为testString;fieldB是数字类型,大于0(不等于0);三个字段为必填字段。
{
"type": "object",
"properties": {
"fieldA": {
"type": "string",
"enum": [
"testString"
]
},
"fieldB": {
"type": "number",
"exclusiveMinimum": 0
}
},
"required": [
"fieldA",
"fieldB",
"fieldC"
]
}
{
"fieldA": "test",
"fieldB": 0
}
在线验证地址 https://jsonschemalint.com/#!/version/draft-07/markup/json
json schema 存在多个版本,当前最新版本为draft-07。 不同版本之间规范也不同。部分实现json schema框架默认draft4,可能大家使用过程中会造成麻烦。为了统一规范,可以在后续实现中,显式设置版本号。当然也可是每条检验规范显示设置一个版本号,不过那样太麻烦了。
版本 | exclusiveMaximum | exclusiveMaximum |
---|---|---|
draft-04 | 取值为 true false,与minimum结合使用,举例 a>0,则exclusiveMaximum: true, minimum:0 | 取值为 true false,与maximum结合使用,举例 a<0,则exclusiveMaximum: true, maximum:0 |
draft-07 | 数字,举例 a>0,则exclusiveMinimum : 0 | 数字,举例 a<0,则exclusiveMaximum: 0 |
第二部分只是定义了一个规范,现在讲述一下如何实现。虽然JSON schema是脱离语言的,但是实现还是跟语言相关的。
框架 | 地址 | 介绍 | 是否选用 |
---|---|---|---|
ajv | https://github.com/ajv-validator/ajv | draft-07, -06, -04 for Node.js and browsers - supports custom keywords and $data reference (MIT) | 选用 |
djv | https://github.com/korzio/djv | draft-06, -04 for Node.js and browsers (MIT) | |
Hyperjump JSV | https://github.com/jdesrosiers/json-schema | 2019-09, draft-07, -06, -04 Built for Node.js and browsers. Includes support for custom vocabularies. (MIT) | |
vue-vuelidate-jsonschema | https://github.com/mokkabonna/vue-vuelidate-jsonschema | draft-06 (MIT) | |
@cfworker/json-schema | @cfworker/json-schema | 2019-09, draft-07, -06, -04 Built for Cloudflare workers, browsers, and Node.js (MIT) |
选用ajv。ajv 是最优的选择。
ajv 是对 JSON Schema 支持最全的一个库;
性能在现有的库中也很优越, 排在第二位,;
排第一位 djv 没有实现 JSON Schema 的最新特性, 我们需要使用最新版规范draft-07;
ajv 与 djv 性能上很接近, 且 ajv比djv的性能更优越;
ajv的社区比djv活跃;
npm install ajv
// or ESM/TypeScript import
import Ajv from "ajv"
// Node.js require:
const Ajv = require("ajv")
const ajv = new Ajv() // options can be passed, e.g. {allErrors: true}
// 规范
const schema = {
type: "object",
properties: {
r93#:{ type:"string", enum:["a"]},
r94#:{ type:"string", enum:["b"]},
r156#:{anyOf:[{type:"number",exclusiveMinimum:0},{type:"number",exclusiveMaximum:0}]},
},
required: ["r93#","r94#",,"r156#"],
additionalProperties: false,
}
// 待校验的数据
const data = {
r156#: 0,
r93#: "1",
r94#: "b",
}
const validate = ajv.compile(schema)
const valid = validate(data)
if (!valid) console.log(validate.errors)
框架 | 地址 | 介绍 | 是否选用 |
---|---|---|---|
networknt/json-schema-validator | https://github.com/networknt/json-schema-validator | draft-07, -06, -04 Support OpenAPI 3.0 with Jackson parser (Apache License 2.0) | 选用 |
everit-org/json-schema | everit-org/json-schema | draft-07, -06, -04 (Apache License 2.0) | |
snowy-json | https://github.com/ssilverman/snowy-json | 2019-09, draft-07, -06 Uses Maven for the project and Gson under the hood. (GNU Affero General Public License v3.0) |
后端选用networknt框架。根据框架比较,
1.networknt是耗时最少的;
2.支持jackson;
3.最小侵入,只引入一个jar包,不会造成冲突;
3.支持高版本jdk,jdk1.8以上,;
4.社区比较活跃。
<dependency>
<groupId>com.networkntgroupId>
<artifactId>json-schema-validatorartifactId>
<version>1.0.72version>
dependency>
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersion;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
public class JsonSchemaUtil {
public static boolean getPatternIdByRule(String checkExpression, JsonNode json) {
if (StringUtils.isBlank(checkExpression)) {
return false;
}
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(checkExpression);
// 需要明确指定版本为draft7
JsonSchemaFactory validatorFactory = JsonSchemaFactory
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7))
.objectMapper(mapper)
.build();
JsonSchema newSchema = validatorFactory.getSchema(rootNode);
if (CollectionUtils.isNotEmpty(newSchema.validate(json))) {
return false;
} else {
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static void main(String[] args) {
ObjectMapper newMapper = new ObjectMapper();
Map<String, Object> ruleMap = new HashMap<>();
ruleMap.put("r156#", 0);
ruleMap.put("r93#", "1");
ruleMap.put("r94#", "b");
JsonNode json = newMapper.valueToTree(ruleMap);
System.out.println("空对象 校验成功 " + getPatternIdByRule("{}", json));
System.out.println("r156# 校验失败 " + getPatternIdByRule("{ \"type\":\"object\", \"properties\":{ \"r156#\":{\"anyOf\":[{\"type\":\"number\",\"exclusiveMinimum\":0},{\"type\":\"number\",\"exclusiveMaximum\":0}]}}, \"required\":[\"r156#\"] }", json));
System.out.println("r93# 校验失败 " + getPatternIdByRule("{ \"type\":\"object\", \"properties\":{ \"r93#\":{ \"type\":\"string\", \"enum\":[\"a\"] } },\"required\": [\"r93#\"] } "
, json));
System.out.println("r94# 校验成功 " + getPatternIdByRule("{ \"type\":\"object\", \"properties\":{ \"r94#\":{ \"type\":\"string\", \"enum\":[\"b\"] } },\"required\": [\"r94#\"] } "
, json));
}
}
#四、坑
有些坑是要避开的
不同版本的json schema规范是不一样的,可详见 2.3 版本的 介绍,当前最新版本是draft07。举例,后端networknt框架 可调用builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7))指定
通过比较,ajv 和 networknt 是目前最好的选择。当然大家可以根据实际情况选择
3.不同框架使用的JSONjar一样吗?
不一样。比如 后端networknt框架使用jackson, 后端everit框架使用org.json
json schema介绍 https://blog.csdn.net/liuxiao723846/article/details/108523139
json schema介绍 https://cloud.tencent.com/developer/article/1617902
json schema介绍 https://www.shouxicto.com/article/2783.html
在线编辑和校验 https://jsonschemalint.com/#!/version/draft-07/markup/json