var code = ` 'use strict' ` code += ` ${$asString.toString()} ${$asStringNullable.toString()} ${$asStringSmall.toString()} ${$asNumber.toString()} ${$asNumberNullable.toString()} ${$asIntegerNullable.toString()} ${$asNull.toString()} ${$asBoolean.toString()} ${$asBooleanNullable.toString()} `
其次,就会根据 scheme 定义的具体内容生成 stringify 函数的具体代码。而生成的方式也比较简单:通过遍历 scheme。
遍历 scheme 时,根据定义的类型,在对应代码处插入相应的工具函数用于键值转换。例如上面例子中name这个属性:
var accessor = key.indexOf('[') === 0 ? sanitizeKey(key) : `['${sanitizeKey(key)}']` switch (type) { case 'null': code += ` json += $asNull() ` break case 'string': code += nullable ? `json += obj${accessor} === null ? null : $asString(obj${accessor})` : `json += $asString(obj${accessor})` break case 'integer': code += nullable ? `json += obj${accessor} === null ? null : $asInteger(obj${accessor})` : `json += $asInteger(obj${accessor})` break ……
上面代码中的code变量保存的就是最后生成的函数体的代码串。由于在 scheme 定义中,name为string类型,且不为空,所以会在code中添加如下一段代码字符串:
"json += $asString(obj['name'])"
由于还需要处理数组、及联对象等复杂情况,实际的代码省略了很多。
然后,生成的完整的code字符串大致如下:
function $asString(str) { // …… } function $asStringNullable(str) { // …… } function $asStringSmall(str) { // …… } function $asNumber(i) { // …… } function $asNumberNullable(i) { // …… } /* 以上是一系列通用的键值转换方法 */ /* $main 就是 stringify 的主体函数 */ function $main(input) { var obj = typeof input.toJSON === 'function' ? input.toJSON() : input var json = '{' var addComma = false if (obj['name'] !== undefined) { if (addComma) { json += ',' } addComma = true json += '"name":' json += $asString(obj['name']) } // …… 其他属性(status、working)的拼接 json += '}' return json } return $main
最后,将code字符串传入 Function 构造函数来创建相应的 stringify 函数。
// dependencies 主要用于处理包含 anyOf 与 if 语法的情况 dependenciesName.push(code) return (Function.apply(null, dependenciesName).apply(null, dependencies))
3.2. slow-json-stringify
slow-json-stringify 虽然名字叫 "slow",但其实是一个 "fast" 的 stringify 库(命名很调皮)。
The slowest stringifier in the known universe. Just kidding, it's the fastest (:
它的实现比前面提到的 fast-json-stringify 更轻量级,思路也很巧妙。同时它在很多场景下效率会比 fast-json-stringify 更快。
3.2.1. scheme 的定义方式
slow-json-stringify 的 scheme 定义更自然与简单,主要就是将键值替换为类型描述。还是上面这个对象的例子,scheme 会变为
{ name: 'string', status: 'number', working: 'boolean' }
确实非常直观。
3.2.2. stringify 方法的生成
不知道你注意到没有
// scheme { name: 'string', status: 'number', working: 'boolean' } // 目标对象 { name: 'alienzhou', status: 6, working: true }
scheme 和原对象的结构是不是很像?
这种 scheme 的巧妙之处在于,这样定义之后,我们可以先把 scheme JSON.stringify一下,然后“扣去”所有类型值,最后等着我们的就是把实际的值直接填充到 scheme 对应的类型声明处。
具体如何操作呢?
首先,可以直接对 scheme 调用JSON.stringify()来生成基础模版,同时借用JSON.stringify()的第二个参数来作为遍历方法收集属性的访问路径:
let map = {}; const str = JSON.stringify(schema, (prop, value) => { const isArray = Array.isArray(value); if (typeof value !== 'object' || isArray) { if (isArray) { const current = value[0]; arrais.set(prop, current); } _validator(value); map[prop] = _deepPath(schema, prop); props += `"${prop}"|`; } return value; });
此时,map 里收集所有属性的访问路径。同时生成的props可以拼接为匹配相应类型字符还的正则表达式,例如我们这个例子里的正则表达式为/name|status|working"(string|number|boolean|undef)"|\\[(.*?)\\]/。
然后,根据正则表达式来顺序匹配这些属性,替换掉属性类型的字符串,换成统一的占位字符串"__par__",并基于"__par__"拆分字符串: