手写JSON.stringify

更新于 阅读 0

一、JSON.stringify作用

将一个对象或值转换成一个JSON字符串。

console.log(JSON.stringify({ x: 5, y: 6 })); // "{"x":5,"y":6}" console.log(JSON.stringify(true)); // "true" console.log(JSON.stringify(new Date())); // 2020-03-01T13:42:52.105Z console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] })); // "{"x":[10,null,null,null]}"

二、参数

有三个参数:

  • value:要转换的数据
  • replacer: 如果是函数,则在转换过程中每个属性的转换都要调用该函数;如果是数组则只转换数组中包含的属性,如果是null则转换所有属性。
  • space:该参数用于美化json字符串,提高可读性。如果是数字表示空格的个数,最多10个,小于1表示没有空格;是字符串,则用字符串填充。
const data = {
    a: 1,
    b: {
        c: 2,
    }
}
const replacer = function(key, value) {
    if (typeof value === 'number') {
        return value * 2;
    }
    return value;
}
console.log(JSON.stringify(data, replacer, 4));
//"{
//    "a": 2,
//    "b": {
//        "c": 4
//    }
//}"

上面值为number类型时,都乘以2,同时前面填充4个空格。

const data = {
    a: 1,
    b: {
        c: 2,
    }
}
const replacer = function(key, value) {
    if (typeof value === 'number') {
        return value * 2;
    }
    return value;
}
console.log(JSON.stringify(data, replacer, "-"));
//"{
//-"a": 2,
//-"b": {
//--"c": 4
//-}
//}"

三、转换规则

  • 如果数据中有toJSON方法,则使用toJSON返回的值。 比如Date对象就自带toJSON方法。

    const data = { a: 1, b: 2, toJSON: function() { return this.a + this.b; } } // '3'
  • Boolean、Number和String都会转换成相应的原始值。

    const data = { a: 1, b: '2', c: true, }; // '{"a":1,"b":"2","c":true}'
  • undefined、function、Symbol对于JSON都不是有效的值,所以都返回undefined;如果在对象中对应的属性将会被忽略;在数组中都转换成null;

    JSON.stringify(undefined) // undefined JSON.stringify(function(){}) // undefined JSON.stringify(Symbol('foo')) // undefined const data = { a: undefined, b: function() {}, c: Symbol('foo'), d: [true, undefined, function(){}, Symbol.for('foo')], }; //'{"d":[true,null,null,null]}'
  • Symbol作为对象的key时,忽略该属性

    const data = { a: 1, [Symbol.for('foo')]: 'baz' } //'{"a":1}'
  • Date自带toJSON方法,返回的值跟toISOString相同。

  • InfinityNaNnull都会被认为是null。

    const data = { a: 1, b: Infinity, c: NaN, d: null, }; // '{"a":1,"b":null,"c":null,"d":null}'
  • 其他对象(MapSetWeakSetWeakMap等)都会序列化其可枚举属性。

    const data = { a: 1, }; Object.defineProperty(data, 'b', { value: 2, enumerable: false, configurable: true, }); // '{"a":1}' JSON.stringify(new Set([1])); // '{}' // 使用Object.keys(new Set([1])),获取不到任何属性
  • BigInt类型不能序列化,会报类型错误

    JSON.stringify({x: 1n});
    //Uncaught TypeError: Do not know how to serialize a BigInt
    

四、手写JSON.stringify

将以前自己写的实现方式整理了一下,代码如下:

function stringify(data, replacer = null, space = '') { const toString = Object.prototype.toString; const types = { '[object String]': 'string', '[object Number]': 'number', '[object Undefined]': 'undefined', '[object Null]': 'null', '[object Function]': 'function', '[object Array]': 'array', '[object Boolean]': 'bool', '[object Symbol]': 'symbol', '[object BigInt]': 'bigInt', '[object Object]': 'object', '[object RegExp]': 'regexp', '[object Date]': 'date', '[object Set]': 'set', '[object Map]': 'map', }; /** * * @param {*} data * 获取数据类型 */ const getType = data => { return types[toString.call(data)]; } /** * * @param {*} key * @param {*} value * * 处理stringify的replacer * 如果是函数,直接调用函数, * 如果是数组:没有key时或者包含key时返回数据本身,否者返回undefined; */ const replacerCallback = function (key = '', value) { let type = getType(replacer); switch (type) { case 'function': return replacer(key, value); case 'array': return ('' === key || replacer.includes(key)) ? value : undefined; default: return value; } } /** * 获取填充字符串 */ const getSpaceStr = function () { if (getType(space) === 'number') { return Array(space).fill(' ').join(''); } if (getType(space) === 'string') { return space; } return ''; } // 初始化填充字符串; const spaceStr = getSpaceStr(); const needBeautify = spaceStr.length > 0 ? true : false; // 获取填充的字符串 const getPadStr = (level) => "\n" + Array(level).fill(spaceStr).join(''); // 格式化字符串 const formatResult = function (data, level = 0) { if (!needBeautify || 0 === level) { return data; } return getPadStr(level) + data; } /** * * @param {*} data * @param {*} level * 格式化对象 */ const formatObject = function (data, level = 0) { if (data.length === 0) { return formatResult('{}', level); } return formatResult('{', level) + data.map(item => formatResult(item, level + 1)) + ((needBeautify && level === 0) ? "\n" : '') + formatResult('}', level); } /** * * @param {*} data : 数据本身 * @param {*} level: 数据所在的嵌套层数 * 格式化数组 */ const formArray = function(data, level = 0) { if (data.length === 0) { return formatResult('[]', level); } const res = ('[' + data.map(item => formatResult(item, level + 1)) + ((needBeautify && level === 0) ? "\n" : '') + formatResult(']', level)); const reg = new RegExp(getPadStr(level + 1) + getPadStr(level + 1), 'g'); const newStr = getPadStr(level + 1); return res.replace(reg, newStr); } /** * * @param {*} key : 数据的键 * @param {*} origin : 数据的值 * @param {*} level : 数据在原始数据中的嵌套层数 */ const translate = function (key, origin, level) { let data = replacerCallback(key, origin); switch (getType(data)) { case 'bool': return `${data}`; case 'string': return `"${data}"`; case 'number': if (data == Infinity || isNaN(data)) { return 'null'; } return `${data}`; case 'undefined': case 'function': case 'symbol': return 'undefined'; case 'null': return 'null'; case 'bigInt': throw new TypeError('BigInt can not seriallied'); case 'array': const result = data.map((item, index) => { let res = translate('', item, level + 1); switch (res) { case 'undefined': return 'null'; default: return res; } }); return formArray(result, level); default: if ('toJSON' in data) { return data.toJSON(); } let res = []; for (let [key, value] of Object.entries(data)) { if ('symbol' === getType(key)) { continue; } let tmp = translate(key, value, level + 1); if ('undefined' === tmp) { continue; } // 对象需要格式化时,value前面有一个空格 res.push(`"${key}":${needBeautify ? ' ' : ''}${tmp}`); } return formatObject(res, level); } } return translate('', data, 0); } // 测试数据 const data = { a: 2, b: { y: 'y', }, c: Symbol('foo'), [Symbol.for('foo')]: 'd', e: function(){}, x: [ { y: 'y', a: '3' }, { p: 'p', q: 'q', }, undefined, null, NaN, "foo", new Set([1]), new WeakSet(), new Map([{a: 1}]), new WeakMap([[{a:1}, 2]]), function(){}, ], }; const replacer = function (key, value) { if (typeof value === 'number') { return 2 * value; } return value; } console.log(stringify(data)); //'{"a":2,"b":{"y":"y"},"x":[{"y":"y","a":"3"},{"p":"p","q":"q"},null,null,null,"foo",{},{},{},{},null]}' console.log(stringify(data, replacer)); //'{"a":4,"b":{"y":"y"},"x":[{"y":"y","a":"3"},{"p":"p","q":"q"},null,null,null,"foo",{},{},{},{},null]}' console.log(stringify(data, ['a', 'x'])); // '{"a":2,"x":[{"a":"3"},{},null,null,null,"foo",{},{},{},{},null]}' console.log(stringify(data, ['a', 'x'], '--')); // '{ //--"a": 2, //--"x": [ //----{ //------"a": "3" //----}, //----{}, //----null, //----null, //----null, //----"foo", //----{}, //----{}, //----{}, //----{}, //----null //--] //}'

以上是自己写的实现stringify的全部代码,如果代码有不对的地方希望指出。 源码