JavaScript 中的 `JSON` 类

JavaScript 中的 `JSON` 类

本文介绍了 JavaScript 中的 JSON 类。

我们将通过实例讲解 JavaScript 中的 JSON 类。

YouTube Video

JavaScript 中的 JSON

JSON 对象主要有两个方法。JSON.stringify() 用于将对象转换为 JSON 字符串,JSON.parse() 用于从 JSON 字符串创建对象。JSON 是一种只能表达部分 JavaScript 值的数据交换格式。函数、undefinedSymbol 和循环引用无法直接转换为 JSON。

JSON.stringify()

首先,下面是一个将对象转换为 JSON 字符串的基本示例。

1// Convert a JavaScript object to a JSON string.
2const obj = { name: "Alice", age: 30, active: true };
3const jsonString = JSON.stringify(obj);
4console.log(jsonString); // {"name":"Alice","age":30,"active":true}
  • 这段代码是将普通对象转换为 JSON 字符串的最基本示例。JSON.stringify 会同步返回字符串。

JSON.stringify 的第二个参数(replacer)

通过指定 JSON.stringify 的第二个参数 replacer,可以精细控制转换规则。以下是用数组指定只保留特定属性的示例。

1// Use a replacer array to include only specific properties during stringification.
2const user = { id: 1, name: "Bob", password: "secret", role: "admin" };
3const safeJson = JSON.stringify(user, ["id", "name", "role"]);
4console.log(safeJson); // {"id":1,"name":"Bob","role":"admin"}
  • 在此示例中,password 被排除,以便安全地输出日志。当你想为安全原因移除敏感信息时这很有用。

replacer 作为函数使用(用于值的转换和过滤)

如果需要更高的灵活性,可以提供一个函数来处理每个键和值。下面是将 Date 保留为 ISO 字符串的示例。

 1// Use a replacer function to customize serialization.
 2const data = { name: "Carol", joined: new Date("2020-01-01T12:00:00Z") };
 3const jsonCustom = JSON.stringify(data, (key, value) => {
 4  // Convert Date objects to ISO strings explicitly
 5  if (this && this[key] instanceof Date) {
 6    return this[key].toISOString();
 7  }
 8  return value;
 9});
10console.log(jsonCustom); // {"name":"Carol","joined":"2020-01-01T12:00:00.000Z"}
  • 使用函数类型的 replacer 时,传入键和值,返回值会被用于最终的 JSON 输出。this 指向父对象,因此也可以用于嵌套转换。

JSON.parse() 的基础用法

JSON.parse 会把 JSON 字符串转换回对象。请用 try/catch 包裹,以便处理无效的 JSON 数据。

1// Parse a JSON string into an object and handle parsing errors.
2const jsonText = '{"title":"Example","count":42}';
3try {
4  const parsed = JSON.parse(jsonText);
5  console.log(parsed.title); // Example
6} catch (err) {
7  console.error("Invalid JSON:", err.message);
8}
  • 如果字符串无效,会抛出异常,因此处理外部数据时应始终包括异常处理。

使用 reviver 自定义还原(还原 Date 的示例)

通过使用 JSON.parse 的第二个参数 reviver,可以进行值的还原。下面是将存储的字符串日期还原为 Date 对象的示例。

 1// Use a reviver to turn ISO date strings back into Date objects.
 2const jsonWithDate = '{"name":"Dana","joined":"2021-06-15T09:00:00.000Z"}';
 3const obj = JSON.parse(jsonWithDate, (key, value) => {
 4  // A simple check for ISO date-like strings
 5  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
 6    return new Date(value);
 7  }
 8  return value;
 9});
10console.log(obj.joined instanceof Date); // true
  • reviver 会为每个键调用,返回值会被用于最终的对象中。请根据你的应用场景严格校验日期格式。

美化输出与 space 参数

为了可读性而缩进输出,可在 JSON.stringify 的第三个参数传入数字或字符串。

1// Pretty-print an object with 2-space indentation for readability.
2const config = { host: "localhost", port: 3000, debug: true };
3const pretty = JSON.stringify(config, null, 2);
4console.log(pretty);
5/* {
6  "host": "localhost",
7  "port": 3000,
8  "debug": true
9} */
  • 对于需要人工阅读的 JSON(如日志或配置文件输出),指定缩进会更易读。但请注意,这会增加数据体积。

如何处理循环引用

如果存在循环引用,JSON.stringify 会抛出 TypeError。常用的解决方法是通过 seen 集合创建 replacer,以避免循环引用。

 1// Safely stringify objects that may contain circular references.
 2function safeStringify(value) {
 3  const seen = new WeakSet();
 4  return JSON.stringify(value, (key, val) => {
 5    if (val && typeof val === "object") {
 6      if (seen.has(val)) return "[Circular]";
 7      seen.add(val);
 8    }
 9    return val;
10  });
11}
12
13const a = { name: "A" };
14a.self = a;
15console.log(safeStringify(a)); // {"name":"A","self":"[Circular]"}
  • 使用 WeakSet 可以检测出循环引用,同时避免内存泄漏。遇到循环引用时,将返回 "[Circular]"。你也可以用引用ID来代替 "[Circular]"

使用 toJSON 方法自定义序列化

如果对象上定义了 toJSON 方法,JSON.stringify 会使用其返回值。当你想为每种类型嵌入转换规则时,这很有用。

 1// Define toJSON on a class to customize its JSON representation.
 2class Point {
 3  constructor(x, y) {
 4    this.x = x;
 5    this.y = y;
 6  }
 7  toJSON() {
 8    // This will be used by JSON.stringify
 9    return { x: this.x, y: this.y, type: "Point" };
10  }
11}
12
13const p = new Point(10, 20);
14console.log(JSON.stringify(p)); // {"x":10,"y":20,"type":"Point"}
  • 通过 toJSON 可以在对象级别定义序列化规则,比起使用 replacer 更本地化且直观。

JSON 的限制和注意事项

不能被转换为 JSON 的典型值有 undefined、函数、SymbolBigInt 以及循环引用。还要注意数值精度问题(如大整数和 IEEE 浮点数舍入)。

 1// Demonstrate values that can't be represented in JSON.
 2const sample = {
 3  a: undefined,
 4  b: function () {},
 5  c: Symbol("s"),
 6  d: 9007199254740993n // BigInt (note: JSON.stringify will throw on BigInt)
 7};
 8// JSON.stringify will throw for BigInt and will drop undefined, functions, symbols in objects.
 9try {
10  console.log(JSON.stringify(sample));
11} catch (err) {
12  console.error("Error during stringify:", err.message);
13}
  • 处理 BigInt 时,可以转为字符串,或者用自定义 replacertoJSON 来定义其表示方式。

安全性(处理不受信任的 JSON 时)

JSON.parse 本身是安全的,比 eval 更安全,但直接信任并使用解析出的对象(比如做属性访问或数据库查询)是危险的。务必校验并检查数据是否符合预期的结构。

1// Parse external JSON and validate expected properties before use.
2const external = '{"username":"eve","role":"user"}';
3const parsed = JSON.parse(external);
4if (typeof parsed.username === "string" && ["user","admin"].includes(parsed.role)) {
5  console.log("Safe to use parsed.username and parsed.role.");
6} else {
7  throw new Error("Invalid payload");
8}
  • 对于输入校验(结构校验),可以使用如 ajv 这样的库来保障健壮性。

性能考虑

频繁序列化和反序列化大对象会加重 CPU 和内存负担。如有需要,可以只传递差异、使用二进制格式(如 MessagePack)或采用流方式(依次处理数据)。在浏览器环境下,可以使用 structuredClone (用于复制)或在 postMessage 中用可转移对象。

具体建议(简要)

你也可以考虑如下要点:。

  • 对于日志,避免美化输出,使用单行 JSON 以减小体积。
  • 频繁序列化的对象应预先精简,去除不必要的属性。
  • replacer 最小化遍历并排除无关属性。

实用示例:API 请求的发送/接收流程

最后,以下是顺序演示如何安全地将对象转换为 JSON 发送到服务器,以及如何从响应中还原日期。

 1// Prepare payload, stringify safely, send via fetch, and revive dates on response.
 2async function sendUserUpdate(url, user) {
 3  // Remove sensitive info before sending
 4  const payload = JSON.stringify(user, (k, v) => (k === "password" ? undefined : v));
 5  const res = await fetch(url, {
 6    method: "POST",
 7    headers: { "Content-Type": "application/json" },
 8    body: payload
 9  });
10
11  const text = await res.text();
12  // Reviver: convert ISO date strings back to Date
13  const data = JSON.parse(text, (key, value) => {
14    if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
15      return new Date(value);
16    }
17    return value;
18  });
19  return data;
20}
  • 在本例中,发送前排除了 password,收到后通过 reviver 还原日期。实际操作时,还需添加错误处理和超时处理。

总结

使用 JSON.stringify()JSON.parse() 可以实现对象与字符串之间的转换。你可以通过 replacerreviver 自定义转换过程,也可以在类中实现 toJSON 来单独控制每个对象的序列化。循环引用、BigInt、函数等不能直接处理,需要预先特殊处理。

务必验证外部输入,确保敏感信息不被记录到日志或传输中。若数据量较大,避免美化输出、考虑差分传输或使用二进制格式会更加高效。

您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。

YouTube Video