Skip to content

JSON.stringify 实现

描述

JSON.stringify() 将 JavaScript 对象或值转换为 JSON 字符串。

转换规则

输入类型输出
数字对应数字字符串
字符串带引号的字符串
布尔值"true" / "false"
null"null"
数组JSON 数组字符串
对象JSON 对象字符串
undefinedundefined(被忽略)
函数undefined(被忽略)
Symbolundefined(被忽略)
NaN/Infinity"null"
循环引用抛出 TypeError

实现代码

基础版

js
JSON.myStringify = function (value, replacer, space) {
    const seen = new WeakSet();

    function stringify(val, indent = "") {
        if (val === null) return "null";
        if (typeof val === "undefined") return undefined;
        if (typeof val === "function") return undefined;
        if (typeof val === "symbol") return undefined;

        if (typeof val === "number") {
            if (Number.isNaN(val) || !Number.isFinite(val)) {
                return "null";
            }
            return String(val);
        }

        if (typeof val === "boolean") {
            return String(val);
        }

        if (typeof val === "string") {
            return `"${val.replace(/"/g, '\\"')}"`;
        }

        if (Array.isArray(val)) {
            if (seen.has(val)) {
                throw new TypeError("Converting circular reference to JSON");
            }
            seen.add(val);
            const items = val.map((item) => stringify(item, indent));
            seen.delete(val);
            return `[${items.join(",")}]`;
        }

        if (typeof val === "object") {
            if (seen.has(val)) {
                throw new TypeError("Converting circular reference to JSON");
            }
            seen.add(val);
            const keys = Object.keys(val);
            const pairs = keys
                .filter((key) => val[key] !== undefined && typeof val[key] !== "function")
                .map((key) => `"${key}":${stringify(val[key], indent)}`);
            seen.delete(val);
            return `{${pairs.join(",")}}`;
        }
    }

    return stringify(value);
};

完整版(支持 replacer 和 space)

js
JSON.myStringify = function (value, replacer, space) {
    const seen = new WeakSet();

    function stringify(val, indent = "") {
        if (val === null) return "null";
        if (typeof val === "undefined") return undefined;
        if (typeof val === "function") return undefined;
        if (typeof val === "symbol") return undefined;

        if (typeof val === "number") {
            if (Number.isNaN(val) || !Number.isFinite(val)) {
                return "null";
            }
            return String(val);
        }

        if (typeof val === "boolean") {
            return String(val);
        }

        if (typeof val === "string") {
            return `"${escapeString(val)}"`;
        }

        if (Array.isArray(val)) {
            if (seen.has(val)) {
                throw new TypeError("Converting circular reference to JSON");
            }
            seen.add(val);
            const items = val.map((item) => {
                const str = stringify(item, indent);
                return str === undefined ? "null" : str;
            });
            seen.delete(val);
            const bracketSpace = space ? "\n" + indent + "  " : " ";
            return "[" + items.join("," + bracketSpace) + "]";
        }

        if (typeof val === "object") {
            if (seen.has(val)) {
                throw new TypeError("Converting circular reference to JSON");
            }
            seen.add(val);

            const keys = Object.keys(val);
            let filteredKeys = keys;

            if (replacer && Array.isArray(replacer)) {
                filteredKeys = keys.filter((key) => replacer.includes(key));
            }

            const pairs = filteredKeys
                .filter((key) => {
                    const valProp = val[key];
                    return valProp !== undefined && typeof valProp !== "function" && typeof valProp !== "symbol";
                })
                .map((key) => {
                    const strKey = `"${escapeString(key)}":${stringify(val[key], indent)}`;
                    return strKey;
                });

            seen.delete(val);

            const bracketSpace = space ? "\n" + indent + "  " : " ";
            return "{" + pairs.join("," + bracketSpace) + "}";
        }
    }

    return stringify(value);
};

function escapeString(str) {
    return str
        .replace(/"/g, '\\"')
        .replace(/\\/g, "\\\\")
        .replace(/\n/g, "\\n")
        .replace(/\r/g, "\\r")
        .replace(/\t/g, "\\t");
}

使用示例

js
const obj = {
    name: "张三",
    age: 18,
    isStudent: false,
    hobbies: ["读书", "运动"],
    address: {
        city: "北京",
    },
    sayHello: function () {
        console.log("hi");
    },
    undefinedProp: undefined,
    symbolProp: Symbol("test"),
    nullProp: null,
    nanProp: NaN,
    infinityProp: Infinity,
};

console.log(JSON.myStringify(obj));
// {"name":"张三","age":18,"isStudent":false,"hobbies":["读书","运动"],"address":{"city":"北京"},"nullProp":null,"nanProp":null,"infinityProp":null}

面试追问点

JSON.stringify 的第二个参数 replacer

可以是函数或数组:

  • 函数:key-value 过滤或转换
  • 数组:只包含指定 key

JSON.stringify 的第三个参数 space

控制缩进:

  • 数字:缩进空格数
  • 字符串:用作缩进的字符串

循环引用处理

js
const obj = { a: 1 };
obj.self = obj;

JSON.stringify(obj); // TypeError: Converting circular reference to JSON