json-schema我觉得把它类比成json的增强interface就比较好理解。
引用:Json Schema定义了一套词汇和规则,这套词汇和规则用来定义Json元数据,且元数据也是通过Json数据形式表达的。Json元数据定义了Json数据需要满足的规范,规范包括成员、结构、类型、约束等。
此次我们使用一个比较流行的基于json-schema标准实现的库,AJV。还是在vue3的项目上:
cnpm i ajv -S
可以新建个src同级文件夹,里面放json-schema的测试文件,比如json-schema/test.js:
// 官方案例 const Ajv = require("ajv"); const ajv = new Ajv(); // options can be passed, e.g. {allErrors: true} const schema = { type: "object", // 类型 properties: { // 对象上的属性(特征) foo: { type: "number" }, bar: { type: "string" }, }, required: ["foo"], // 必输入项 additionalProperties: false, }; const validate = ajv.compile(schema); const data = { foo: 1, bar: 1, // 故意输错 }; const valid = validate(data); if (!valid) console.log(validate.errors);
cd到文件夹下直接node test.js跑起来,会打印出:
{ instancePath: '/bar', schemaPath: '#/properties/bar/type', keyword: 'type', // 类型错误 params: { type: 'string' }, message: 'must be string' // 错误信息 }
可能在写的过程中,eslint会报错,需要在src同级下创建个.eslintignore文件用来忽略schema-tests文件下的检测:
schema-tests
感觉就是ajv内置的一些字符串和数字的校验规则。
对应官方文档:https://ajv.js.org/guide/formats.html#string-formats
其中的addFormat可以自定义format规则。
类似type、properties等就是关键字。如何自定义关键字的内部逻辑有多种方案。
validate方案:
const Ajv = require("ajv"); const ajv = new Ajv(); const schema = { type: "object", properties: { input: { type: "string", fnCheck: true }, }, }; ajv.addKeyword("fnCheck", { validate(schema, data) { // addKeyword内部逻辑的现实有多种,先看看validate方案 // schema为新增fnCheck关键字的值,为true,data为添加这个关键字的对象的值,input为'1234' if (data === "123") return true; else return false; }, }); const validate = ajv.compile(schema); const data = { input: "1234", }; const valid = validate(data); if (!valid) console.log(validate.errors);
此时运行就会提示没通过fnCheck的校验:
{ instancePath: '/input', schemaPath: '#/properties/input/fnCheck', keyword: 'fnCheck', params: {}, message: 'must pass "fnCheck" keyword validation' }
compile方案:
const Ajv = require("ajv"); const ajv = new Ajv(); const schema = { type: "object", properties: { input: { type: "string", fnCheck: true }, }, }; ajv.addKeyword("fnCheck", { compile(sch, parentSchema) { // sch 为新增key的值,例如fnCheck的true。parentSchema取的是新增key所在的父级内容,也就是{ type: "string", fnCheck: true } return () => true; // 注意的是要返回一个函数,如果你想返回值需要通过返回的函数再返回出去 }, metaSchema: { type: "boolean", // 定义新增key值的类型 }, }); const validate = ajv.compile(schema); const data = { input: "1234", }; const valid = validate(data); if (!valid) console.log(validate.errors);
macro方案:
const Ajv = require("ajv"); const ajv = new Ajv(); const schema = { type: "object", properties: { input: { type: "string", fnCheck: true, minLength: 10, maxLength: 20 }, }, }; ajv.addKeyword("fnCheck", { macro() { // 会把返回的内容全部加到新增key的下面 return { minLength: 10, maxLength: 20, }; }, }); const validate = ajv.compile(schema); const data = { input: "1234", }; const valid = validate(data); if (!valid) console.log(validate.errors);
剩下一个inline方案,虽然性能上是最优的,但是可读性很差,就不记录了。