背景
对于前端程序猿,常见的开发流程是:
- 前后端约定接口
- 后端给出接口文档
- 根据接口编写 TypeScript 类型定义
- 开发完成进行联调
虽然一切顺利,但是上线后还是翻车了,js 报错:cannot read the property 'xx' of null
,很显然前端没有处理空值,接锅吧 TT。但回头一看接口文档,跟后端同学约定的返回对象,但实际返了 null
,这锅不能一个人背。那么怎样才能尽早发现这种问题呢?一种解决方案是:
- 将 TypeScript 类型定义转为 JSON Schema
- 利用 JSON Schema 校验数据正确性
针对以上方案做了一个 demo。
JSON Schema
JSON Schema 是一个 JSON 对象,用来描述和校验 JSON 对象的格式。比如下面这个 JSON Schema:
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "number"
},
"hobby": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"age",
"hobby",
"name"
],
"$schema": "http://json-schema.org/draft-07/schema#",
}
它描述了这样一个 JSON 对象:
- 类型 -
type
是obeject
有四个属性 -
properties
name
:类型为string
age
:类型为number
hobby
:类型为Array
- 属性是否必须 -
required
:name
,age
,hobby
都为必须
下面这个 JSON 对象就是满足这个 JSON Schema 的:
{
"name": "Tom",
"age": 1,
"hobby": ["coding"]
}
可以看出 JSON Schema 还是很好理解的,但其描述语法还是有一定学习成本,这里强烈推荐通过 jsonschema 库的 example 去学习相关语法,另外也可以看 Understanding JSON Schema。
有了 JSON Schema,怎么使用它校验 JSON 对象的合法性呢?这里就用到了刚刚提到的jsonschema 库。简单使用示例如下:
var Validator = require('jsonschema').Validator;
var v = new Validator();
var instance = 4;
var schema = {"type": "number"};
console.log(v.validate(instance, schema));
现在可以根据 JSON Schema 去校验后端返回数据的格式是否正确了,但是为每个接口手动编写 JSON Schema 是不现实的,我们自然会想到能不能将 TypeScript 的接口类型定义转为 JSON Schema?
TypeScript Interface -> JSON Schema
好在已经有 typescript-json-schema 这个库帮我们解决了这个问题,下面给出简单的使用示例:
import path from "path";
import * as TJS from "typescript-json-schema";
const settings: TJS.PartialArgs = {
required: true
};
// optionally pass ts compiler options
const compilerOptions: TJS.CompilerOptions = {
strictNullChecks: true
};
// 解析接口定义文件:index.ts
const program = TJS.getProgramFromFiles(
[path.join(__dirname, './apis/index.ts')],
compilerOptions,
);
// 将"IApi1"这个interface转为schema,传入"*"将转换全部interface
let schema = TJS.generateSchema(program, "IApi1", settings) || {};
一顿操作后就可以将下面这个 interface
转为文章开头给出的示例 JSON Schema:
interface IApi1 {
name: string;
age: number;
hobby: string[];
}
然后再用 node 将刚刚得到的 schema 存成 json 文件:
fs.writeFileSync(path.join(__dirname, "./json-schema", "schema.json"), schema);
接着就可以使用相应的 JSON Schema 对后端数据进行校验了:
import { Validator } from 'jsonschema'
const apiSchema = require('./json-schema/schema.json')
const v = new Validator();
Api1().then(res => {
const validateRes1 = v.validate(res, apiSchema)
console.log(validateRes1);
});
完整的示例可以看:demo
在工程中的实践
1、如何组织散落的接口类型定义?
我个人比较喜欢的方式是:在 apis.ts
文件中统一去写所有的接口定义和类型定义,这样在转换 JSON Schema 时去处理这一个文件即可。
2、怎样自动将类型定义转为 JSON Schema?
使用 husky
,在 pre-commit
阶段去执行转换工作,进一步可以使用 lint-staged
判断当前提交是否涉及到接口定义文件的改动,有改动再执行转换。
3、何时校验数据?
这里我想到两个场景:
- 生产上:对于一些关键接口,在接口返回数据后调用校验逻辑,如果校验有错,需要做两件事:第一是将错误数据转为正确的备用数据,以防页面挂掉;第二是上报错误;
- 测试时:写各种测试用例去测后端接口,校验返回数据的正确性,这样就不用人眼去校验数据是否正确了。