JavaScript 中的 `JSON` 类

JavaScript 中的 `JSON` 类

本文介绍了 JavaScript 中的 JSON 类。

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

YouTube Video

JavaScript 中的 JSON 类

JSON 对象主要有两个方法。JSON.stringify() 用于将对象转换为 JSON 字符串,JSON.parse() 用于从 JSON 字符串创建对象。JSON 是一种只能表达部分 JavaScript 值的数据交换格式。函数、undefined、Symbol 和循环引用无法直接转换为 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、函数、Symbol、BigInt 以及循环引用。还要注意数值精度问题(如大整数和 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 时,可以转为字符串,或者用自定义 replacer 或 toJSON 来定义其表示方式。

安全性(处理不受信任的 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() 可以实现对象与字符串之间的转换。你可以通过 replacer 和 reviver 自定义转换过程,也可以在类中实现 toJSON 来单独控制每个对象的序列化。循环引用、BigInt、函数等不能直接处理,需要预先特殊处理。

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

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

YouTube Video