JSON.stringify()

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

试一试

语法

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(() => {})JSON.stringify(undefined))时可能会返回 undefined
  • 数字 InfinityNaN,以及值 null,都被视为 null。(但与前一点中的值不同,它们永远不会被省略。)
  • 数组被序列化为数组(用方括号括起来)。仅序列化 0 到 length - 1(含)之间的数组索引;其他属性将被忽略。
  • 使用 JSON.rawJSON() 创建的特殊原始 JSON 对象将被序列化为其包含的原始 JSON 文本(通过访问其 rawJSON 属性)。
  • 对于其他对象
    • 所有 Symbol 键属性都将被完全忽略,即使使用 replacer 参数也是如此。
    • 如果该值具有 toJSON() 方法,则它负责定义将序列化哪些数据。在序列化对象时,将序列化调用 toJSON() 方法时返回的值。JSON.stringify() 会使用一个参数(即 key)调用 toJSON,该参数的语义与 replacer 函数的 key 参数相同
      • 如果此对象是属性值,则为属性名称
      • 如果它位于数组中,则为数组中的索引(作为字符串)
      • 如果直接在此对象上调用了 JSON.stringify(),则为空字符串
      Date 对象实现了 toJSON() 方法,该方法返回一个字符串(与 date.toISOString() 相同)。因此,它们将被字符串化为字符串。
    • 仅访问 可枚举的自有属性。这意味着 MapSet 等将变为 "{}"。可以使用 replacer 参数将其序列化为更有用的内容。属性使用与 Object.keys() 相同的算法进行访问,该算法具有明确定义的顺序并且在不同的实现中是稳定的。例如,同一个对象的 JSON.stringify 始终会生成相同的字符串,并且 JSON.parse(JSON.stringify(obj)) 将生成一个与原始对象具有相同键顺序的对象(假设该对象完全可 JSON 序列化)。

replacer 参数

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

作为数组,其元素指示对象中应包含在生成的 JSON 字符串中的属性的名称。仅考虑字符串和数字值;符号键将被忽略。

作为函数,它接受两个参数:正在字符串化的 keyvalue。其中找到 key 的对象作为 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

使用函数作为 replacer

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}"

使用数组作为replacer

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()

实现格式良好的JSON.stringify规范的引擎将使用Unicode转义序列而不是字面量来序列化孤立的代理(任何从U+D800到U+DFFF的代码点)(输出孤立的代理)。在此更改之前,此类字符串无法以有效的UTF-8或UTF-16进行编码

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

但通过此更改,JSON.stringify()使用JSON转义序列表示孤立的代理,这些序列可以以有效的UTF-8或UTF-16进行编码

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

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

规范

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

浏览器兼容性

BCD表仅在浏览器中加载

另请参阅