记前两天遇见的一道面试题,让现场写出 JavaScript 中 stringify
函数的实现。首先写一下自己最开始的思路,然后针对里面的一些问题进行逐步修改,并且引出对 JSON 这种轻量级数据传输格式所拥有数据类型的学习与思考。
首先给定题目
将一个 JSON 格式对象转换为字符串,转换后的结果可以通过 JSON.parse()
方法将该字符串重新转换为一个 JSON 对象。
先简化问题,将数据类型简单划分(并不正确,下文会给描述)为:对象(Object)、数组(Array)和字符串(String)这三种类型。对于 String,只需要简单的对它进行 toString()
调用,并包裹在 "
中处理,对于前两者则需要采取不同操作。考虑到他们的子元素也是 JSON 对象,定义具有递归性,所以代码也采用递归来实现。最初代码如下:
function isArray () {} function isObject () {} function stringify (obj) { let ret = ''; if (!isObject(obj) && !isArray(obj)) { return '"' + obj.toString() + '"'; } else if (isObject(obj)) { ret += '{'; let keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { let key = keys[i]; ret += '"' + key.toString() + '":'; if (!isObject(obj[key]) && !isArray(obj[key])) { ret += '"' + obj[key].toString() + '"'; } else { ret += stringify(obj[key]); } if (i != keys.length - 1) { ret += ','; } } ret += '}'; } else if (isArray(obj)) { ret += '['; for (let i = 0; i < obj.length; i++) { ret += stringify(obj[i]); if (i != obj.length - 1) { ret += ','; } } ret += ']'; } return ret; }
function isArray () {} function isObject () {} function stringify (obj) { letret = ''; if (!isObject(obj) && !isArray(obj)) { return '"' + obj.toString() + '"'; } else if (isObject(obj)) { ret += '{'; letkeys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { letkey = keys[i]; ret += '"' + key.toString() + '":'; if (!isObject(obj[key]) && !isArray(obj[key])) { ret += '"' + obj[key].toString() + '"'; } else { ret += stringify(obj[key]); } if (i != keys.length - 1) { ret += ','; } } ret += '}'; } else if (isArray(obj)) { ret += '['; for (let i = 0; i < obj.length; i++) { ret += stringify(obj[i]); if (i != obj.length - 1) { ret += ','; } } ret += ']'; } return ret; }
代码的思路就是传入一个对象,然后判断它的类型:1. 如果非数组和对象类型,就直接返回它的字符串形式;2. 如果是对象类型,则遍历每个键值对,判断每个键对应值的类型,如果是非字符串,则递归对它调用函数本身,否则直接在结果字符串中拼接他的字符串形式;3. 数组类型类似对象类型处理。这也是自己在面试中完成的版本。
面试官又询问了是否可以保存 function 这种类型,其实这就牵扯到 JSON 这种数据格式所拥有的数据类型问题,JSON 是没有 function 这种数据类型的。其实可以想一下,JSON 的的目的是为了方便数据传输,而且其它语言也都有很多系统提供的库来处理 JSON,但其它语言并没有必要接收 function 这种类型,因为并没有办法直接执行,存储它也没有太大意义。
于是查找 JSON 的 官方定义 或者 这篇文章 ,可以看到 JSON 的数据格式包括:
所以上面的代码是没有针对 Number / Boolean / null 这3种数据类型做处理的。针对这三种类型(其中 Boolean 需要判断 true
和 false
两种情况),只要对他们进行 toString()
处理,而不要在外面包裹双引号即可。最直接的处理方法:使用条件分支判断数据类型,代码实现为:
function isArray () {} function isObject () {} function isString () {} function isNull () {} function stringify (obj) { let ret = ''; if (!isObject(obj) && !isArray(obj)) { if (!isString(obj)) { if (!isNull(obj)) { return obj.toString(); } else { return 'null'; } } else { return '"' + obj.toString() + '"'; } } else if (isObject(obj)) { ret += '{'; let keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { let key = keys[i]; ret += '"' + key.toString() + '":'; if (!isObject(obj[key]) && !isArray(obj[key])) { if (!isString(obj[key])) { if (!isNull(obj[key])) { ret += obj[key].toString(); } else { ret += 'null'; } } else { ret += '"' + obj[key].toString() + '"'; } } else { ret += stringify(obj[key]); } if (i != keys.length - 1) { ret += ','; } } ret += '}'; } else if (isArray(obj)) { ret += '['; for (let i = 0; i < obj.length; i++) { ret += stringify(obj[i]); if (i != obj.length - 1) { ret += ','; } } ret += ']'; } return ret; } let json = {'outter': {'inner': ["1", 2, {'item': 'content', 'null': null}]}}; let result = stringify(json); console.log(result); console.log(JSON.parse(result));
function isArray () {} function isObject () {} function isString () {} function isNull () {} function stringify (obj) { letret = ''; if (!isObject(obj) && !isArray(obj)) { if (!isString(obj)) { if (!isNull(obj)) { return obj.toString(); } else { return 'null'; } } else { return '"' + obj.toString() + '"'; } } else if (isObject(obj)) { ret += '{'; letkeys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { letkey = keys[i]; ret += '"' + key.toString() + '":'; if (!isObject(obj[key]) && !isArray(obj[key])) { if (!isString(obj[key])) { if (!isNull(obj[key])) { ret += obj[key].toString(); } else { ret += 'null'; } } else { ret += '"' + obj[key].toString() + '"'; } } else { ret += stringify(obj[key]); } if (i != keys.length - 1) { ret += ','; } } ret += '}'; } else if (isArray(obj)) { ret += '['; for (let i = 0; i < obj.length; i++) { ret += stringify(obj[i]); if (i != obj.length - 1) { ret += ','; } } ret += ']'; } return ret; } letjson = {'outter': {'inner': ["1", 2, {'item': 'content', 'null': null}]}}; letresult = stringify(json); console.log(result); console.log(JSON.parse(result));
对于 Number / Boolean 可以直接使用 toString()
,对于 null
则需要直接返回 'null'
字符串。以上就是所有实现,但是类型判断处代码使用了较深的 if
分支判断,可以继续优化,而且没有考虑数据合法性校验的问题,待更新。另注意,JSON 中是没有 undefined
这种数据类型的。