JSON.stringify()

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

JSON.stringify() 静态方法将 JavaScript 值转换为 JSON 字符串,如果指定了替换函数,则可以选择替换值;如果指定了替换数组,则可以选择仅包含指定的属性。

试一试

console.log(JSON.stringify({ x: 5, y: 6 }));
// Expected output: '{"x":5,"y":6}'

console.log(
  JSON.stringify([new Number(3), new String("false"), new Boolean(false)]),
);
// Expected output: '[3,"false",false]'

console.log(JSON.stringify({ x: [10, undefined, function () {}, Symbol("")] }));
// Expected output: '{"x":[10,null,null,null]}'

console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)));
// Expected output: '"2006-01-02T15:04:05.000Z"'

语法

js
JSON.stringify(value)
JSON.stringify(value, replacer)
JSON.stringify(value, replacer, space)

参数

value

要转换为 JSON 字符串的值。

replacer 可选

一个改变字符串化过程行为的函数,或者一个指定 value 中要包含在输出中的属性的字符串和数字数组。如果 replacer 是一个数组,则数组中所有不是字符串或数字(无论是原始值还是包装对象)的元素,包括 Symbol 值,都会被完全忽略。如果 replacer 不是函数也不是数组(例如,null 或未提供),则对象的所有字符串键属性都将包含在生成的 JSON 字符串中。

space 可选

一个用于在输出 JSON 字符串中插入空格(包括缩进、换行符等)以提高可读性的字符串或数字。

如果这是一个数字,它表示用于缩进的空格数,最多为 10(即,任何大于 10 的数字都将被视为 10)。小于 1 的值表示不应使用空格。

如果这是一个字符串,该字符串(或者如果字符串超过 10 个字符,则为前 10 个字符)将在每个嵌套对象或数组之前插入。

如果 space 不是字符串或数字(可以是原始值或包装对象)——例如,是 null 或未提供——则不使用任何空格。

返回值

表示给定值的 JSON 字符串,或者 undefined。

异常

TypeError

在以下情况之一中抛出

  • value 包含循环引用。
  • 遇到了 BigInt 值。

描述

JSON.stringify() 将值转换为该值表示的 JSON 符号。值将按如下方式字符串化:

  • BooleanNumberStringBigInt(可通过 Object() 获取)对象在字符串化过程中会根据传统的转换语义转换为相应的原始值。 Symbol 对象(可通过 Object() 获取)被视为普通对象。
  • 尝试序列化 BigInt 值将会抛出错误。但是,如果 BigInt 具有 toJSON() 方法(通过猴子补丁:BigInt.prototype.toJSON = ...),则该方法可以提供序列化结果。此约束确保用户始终通过显式提供的方式来确保正确的序列化(以及非常可能的,其伴随的反序列化)行为。
  • undefinedFunctionSymbol 值不是有效的 JSON 值。如果在转换过程中遇到任何此类值,它们将被省略(当在对象中找到时)或更改为 null(当在数组中找到时)。当传递“纯”值(如 JSON.stringify(() => {})JSON.stringify(undefined))时,JSON.stringify() 可以返回 undefined
  • 数字 InfinityNaN,以及值 null,都被视为 null。(但与上一点中的值不同,它们永远不会被省略。)
  • 数组被序列化为数组(用方括号括起来)。只有介于 0 和 length - 1(包含)之间的数组索引才会被序列化;其他属性将被忽略。
  • 使用 JSON.rawJSON() 创建的特殊原始 JSON 对象将被序列化为它包含的原始 JSON 文本(通过访问其 rawJSON 属性)。
  • 对于其他对象
    • 所有 Symbol 键的属性都将被完全忽略,即使在使用 replacer 参数时也是如此。

    • 如果值具有 toJSON() 方法,则该方法负责定义要序列化的数据。不会直接序列化对象本身,而是会序列化调用 toJSON() 方法后返回的值。JSON.stringify() 会使用一个参数 key 调用 toJSON,该参数的语义与 replacer 函数的 key 参数相同。

      • 如果此对象是属性值,则为属性名
      • 如果它在数组中,则为数组中的索引(以字符串形式)
      • 如果 JSON.stringify() 直接在该对象上调用,则为空字符串

      所有 Temporal 对象都实现了 toJSON() 方法,该方法返回一个字符串(与调用 toString() 相同)。因此,它们将被序列化为字符串。类似地,Date 对象实现了 toJSON(),它返回与 toISOString() 相同的值。

    • 只有 可枚举的自有属性 才会被访问。这意味着 MapSet 等将变成 "{}"。您可以使用 replacer 参数将它们序列化为更有用的内容。

      属性的访问使用与 Object.keys() 相同的算法,该算法具有明确定义的顺序,并且在不同实现之间是稳定的。例如,对同一对象使用 JSON.stringify 总是会生成相同的字符串,而 JSON.parse(JSON.stringify(obj)) 将生成一个键顺序与原始对象相同的对象(假设该对象完全是 JSON 可序列化的)。

替换参数

replacer 参数可以是函数或数组。

作为数组时,其元素指定要在生成的 JSON 字符串中包含的对象属性的名称。只有字符串和数字值会被考虑在内;符号键会被忽略。

作为函数时,它接受两个参数:正在字符串化的 keyvalue。找到键的对象作为 replacerthis 上下文提供。

replacer 函数也会被调用来处理正在字符串化的初始对象,在这种情况下,key 是一个空字符串("")。然后,它会为正在字符串化的对象或数组中的每个属性调用。数组索引将以字符串形式作为 key 提供。当前属性值将被 replacer 的返回值替换用于字符串化。这意味着:

  • 如果您返回一个数字、字符串、布尔值或 null,该值将被直接序列化并用作属性的值。(返回 BigInt 也会抛出错误。)
  • 如果您返回一个 FunctionSymbolundefined,则该属性不会包含在输出中。
  • 如果您返回任何其他对象,该对象将被递归地字符串化,并对每个属性调用 replacer 函数。

注意: 在解析使用 replacer 函数生成的 JSON 时,您可能希望使用 reviver 参数来执行反向操作。

通常,数组元素的索引永远不会改变(即使元素是无效值,如函数,它也会变成 null 而不是被省略)。使用 replacer 函数可以通过返回不同的数组来控制数组元素的顺序。

space 参数

space 参数可用于控制最终字符串中的空格。

  • 如果它是一个数字,字符串化的每个连续级别都将使用此数量的空格进行缩进。
  • 如果它是一个字符串,连续的级别将使用此字符串进行缩进。

每个缩进级别永远不会超过 10 个字符。space 的数字值将被限制为 10,字符串值将被截断为 10 个字符。

示例

使用 JSON.stringify

js
JSON.stringify({}); // '{}'
JSON.stringify(true); // 'true'
JSON.stringify("foo"); // '"foo"'
JSON.stringify([1, "false", false]); // '[1,"false",false]'
JSON.stringify([NaN, null, Infinity]); // '[null,null,null]'
JSON.stringify({ x: 5 }); // '{"x":5}'

JSON.stringify(new Date(1906, 0, 2, 15, 4, 5));
// '"1906-01-02T15:04:05.000Z"'

JSON.stringify({ x: 5, y: 6 });
// '{"x":5,"y":6}'
JSON.stringify([new Number(3), new String("false"), new Boolean(false)]);
// '[3,"false",false]'

// String-keyed array elements are not enumerable and make no sense in JSON
const a = ["foo", "bar"];
a["baz"] = "quux"; // a: [ 0: 'foo', 1: 'bar', baz: 'quux' ]
JSON.stringify(a);
// '["foo","bar"]'

JSON.stringify({ x: [10, undefined, function () {}, Symbol("")] });
// '{"x":[10,null,null,null]}'

// Standard data structures
JSON.stringify([
  new Set([1]),
  new Map([[1, 2]]),
  new WeakSet([{ a: 1 }]),
  new WeakMap([[{ a: 1 }, 2]]),
]);
// '[{},{},{},{}]'

// TypedArray
JSON.stringify([new Int8Array([1]), new Int16Array([1]), new Int32Array([1])]);
// '[{"0":1},{"0":1},{"0":1}]'
JSON.stringify([
  new Uint8Array([1]),
  new Uint8ClampedArray([1]),
  new Uint16Array([1]),
  new Uint32Array([1]),
]);
// '[{"0":1},{"0":1},{"0":1},{"0":1}]'
JSON.stringify([new Float32Array([1]), new Float64Array([1])]);
// '[{"0":1},{"0":1}]'

// toJSON()
JSON.stringify({
  x: 5,
  y: 6,
  toJSON() {
    return this.x + this.y;
  },
});
// '11'

// Symbols:
JSON.stringify({ x: undefined, y: Object, z: Symbol("") });
// '{}'
JSON.stringify({ [Symbol("foo")]: "foo" });
// '{}'
JSON.stringify({ [Symbol.for("foo")]: "foo" }, [Symbol.for("foo")]);
// '{}'
JSON.stringify({ [Symbol.for("foo")]: "foo" }, (k, v) => {
  if (typeof k === "symbol") {
    return "a symbol";
  }
});
// undefined

// Non-enumerable properties:
JSON.stringify(
  Object.create(null, {
    x: { value: "x", enumerable: false },
    y: { value: "y", enumerable: true },
  }),
);
// '{"y":"y"}'

// BigInt values throw
JSON.stringify({ x: 2n });
// TypeError: BigInt value can't be serialized in JSON

将函数用作替换器

js
function replacer(key, value) {
  // Filtering out properties
  if (typeof value === "string") {
    return undefined;
  }
  return value;
}

const foo = {
  foundation: "Mozilla",
  model: "box",
  week: 45,
  transport: "car",
  month: 7,
};
JSON.stringify(foo, replacer);
// '{"week":45,"month":7}'

如果您希望 replacer 区分初始对象和具有空字符串属性的键(因为两者都会得到空字符串作为键,并可能有一个对象作为值),您需要跟踪迭代次数(如果超出第一次迭代,则是一个真正的空字符串键)。

js
function makeReplacer() {
  let isInitial = true;

  return (key, value) => {
    if (isInitial) {
      isInitial = false;
      return value;
    }
    if (key === "") {
      // Omit all properties with name "" (except the initial object)
      return undefined;
    }
    return value;
  };
}

const replacer = makeReplacer();
console.log(JSON.stringify({ "": 1, b: 2 }, replacer)); // "{"b":2}"

将数组用作替换器

js
const foo = {
  foundation: "Mozilla",
  model: "box",
  week: 45,
  transport: "car",
  month: 7,
};

JSON.stringify(foo, ["week", "month"]);
// '{"week":45,"month":7}', only keep "week" and "month" properties

使用 space 参数

使用一个空格缩进输出

js
console.log(JSON.stringify({ a: 2 }, null, " "));
/*
{
 "a": 2
}
*/

使用制表符模仿标准的漂亮打印外观

js
console.log(JSON.stringify({ uno: 1, dos: 2 }, null, "\t"));
/*
{
	"uno": 1,
	"dos": 2
}
*/

toJSON() 行为

为对象定义 toJSON() 可以覆盖其序列化行为。

js
const obj = {
  data: "data",

  toJSON(key) {
    return key ? `Now I am a nested object under key '${key}'` : this;
  },
};

JSON.stringify(obj);
// '{"data":"data"}'

JSON.stringify({ obj });
// '{"obj":"Now I am a nested object under key 'obj'"}'

JSON.stringify([obj]);
// '["Now I am a nested object under key '0'"]'

序列化循环引用的问题

由于 JSON 格式不支持对象引用(尽管存在 IETF 草案),因此尝试编码包含循环引用的对象时会抛出 TypeError

js
const circularReference = {};
circularReference.myself = circularReference;

// Serializing circular references throws "TypeError: cyclic object value"
JSON.stringify(circularReference);

要序列化循环引用,您可以使用支持它们的库(例如,Douglas Crockford 的 cycle.js)或自己实现解决方案,这需要查找并替换(或删除)循环引用为可序列化值。

如果您使用 JSON.stringify() 来深度复制对象,您可能更倾向于使用 structuredClone(),它支持循环引用。JavaScript 引擎的二进制序列化 API,例如 v8.serialize(),也支持循环引用。

将 JSON.stringify() 与 localStorage 结合使用

在您希望存储用户创建的对象并允许其在浏览器关闭后仍然能够恢复的情况下,以下示例是 JSON.stringify() 适用性的模型。

js
// Creating an example of JSON
const session = {
  screens: [],
  state: true,
};
session.screens.push({ name: "screenA", width: 450, height: 250 });
session.screens.push({ name: "screenB", width: 650, height: 350 });
session.screens.push({ name: "screenC", width: 750, height: 120 });
session.screens.push({ name: "screenD", width: 250, height: 60 });
session.screens.push({ name: "screenE", width: 390, height: 120 });
session.screens.push({ name: "screenF", width: 1240, height: 650 });

// Converting the JSON string with JSON.stringify()
// then saving with localStorage in the name of session
localStorage.setItem("session", JSON.stringify(session));

// Example of how to transform the String generated through
// JSON.stringify() and saved in localStorage in JSON object again
const restoredSession = JSON.parse(localStorage.getItem("session"));

// Now restoredSession variable contains the object that was saved
// in localStorage
console.log(restoredSession);

符合规范的 JSON.stringify()

实现 well-formed JSON.stringify 规范的引擎将使用 Unicode 转义序列来字符串化独立的代理(任何代码点从 U+D800 到 U+DFFF),而不是直接输出(输出独立的代理)。在此更改之前,此类字符串无法在有效的 UTF-8 或 UTF-16 中编码。

js
JSON.stringify("\uD800"); // '"�"'

但经过此更改后,JSON.stringify() 会使用 可以 在有效 UTF-8 或 UTF-16 中编码的 JSON 转义序列来表示独立的代理。

js
JSON.stringify("\uD800"); // '"\\ud800"'

此更改应该是向后兼容的,只要您将 JSON.stringify() 的结果传递给接受任何有效 JSON 文本的 API(如 JSON.parse()),因为它们会将独立的代理的 Unicode 转义视为与独立的代理本身相同。*只有*当您直接解释 JSON.stringify() 的结果时,您才需要仔细处理这些代码点的 JSON.stringify() 的两种可能编码。

规范

规范
ECMAScript® 2026 语言规范
# sec-json.stringify

浏览器兼容性

另见