天天看点

手写JSON.stringify

一、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相同。
  • Infinity、NaN和null都会被认为是null。
    const data = {
        a: 1,
        b: Infinity,
        c: NaN,
        d: null,
    };
    // '{"a":1,"b":null,"c":null,"d":null}'
               
  • 其他对象(Map、Set、WeakSet、WeakMap等)都会序列化其可枚举属性。
    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的全部代码,如果代码有不对的地方希望指出。

源码

原文地址