JavaScript JSON

JSON(JavaScript Object Notation)是互联网数据交换的通用语言——前后端通信、配置文件、API 响应,几乎都用 JSON。它本质就是"JS 对象的字符串表示",所以 JS 处理 JSON 天然方便。这一章我们彻底掌握 JSON 的两个核心方法以及它的"坑"。

1. JSON 是什么

JSON 不是 JS 独有的——它是一种跨语言的文本格式,几乎所有编程语言都支持。它源于 JS 的对象字面量语法,但更严格:

// JSON 是什么?
// JSON(JavaScript Object Notation)是"JS 对象的字符串表示"
// 它是互联网上数据交换的标准格式

// JS 对象 vs JSON 字符串
const user = {
  name: "小明",
  age: 20,
  isStudent: true,
  hobbies: ["读书", "写代码"]
};

// 对象 → JSON 字符串(序列化)
const json = JSON.stringify(user);
console.log(json);
// 输出: {"name":"小明","age":20,"isStudent":true,"hobbies":["读书","写代码"]}
console.log(typeof json);   // "string"

// JSON 字符串 → 对象(反序列化)
const parsed = JSON.parse(json);
console.log(parsed.name);   // 小明
console.log(parsed.age);    // 20

记住两个核心方法:

2. JSON 与 JS 对象的区别(JSON 更严格)

这是新手最容易踩的坑——不是所有 JS 对象字面量都是合法 JSON:

// JSON 与 JS 对象的区别(JSON 更严格)

// 1. JSON 键必须用双引号
const validJson = '{"name": "小明"}';     // ✅
// const badJson = "{name: '小明'}";       // ❌ 解析失败

// 2. JSON 字符串只能用双引号
const valid = '{"name": "小明"}';         // ✅
// const bad = "{'name': '小明'}";         // ❌
// const bad2 = '{"name": '小明'}';    // ❌ 单引号也不行

// 3. JSON 不允许尾随逗号
const valid2 = '{"a": 1, "b": 2}';        // ✅
// const bad3 = '{"a": 1, "b": 2,}';       // ❌ 最后一个后面不能有逗号

// 4. JSON 不允许注释
// const bad4 = '{ // 注释\n "a": 1 }';   // ❌
// const bad5 = '{ /* 注释 */ "a": 1 }';   // ❌

// 5. JSON 支持的类型有限
// 数字、字符串、布尔、null、数组、对象
// 不支持:undefined、函数、Symbol、Date(会转字符串)、RegExp

JSON 的五大禁忌:

  1. 键必须用双引号(不是单引号,不能省略)。
  2. 字符串值必须用双引号
  3. 不能有尾随逗号(最后一个属性后多写逗号会报错)。
  4. 不能有注释
  5. 只支持有限类型:数字、字符串、布尔、null、数组、对象。不支持 undefined、函数、Symbol、Date(会变字符串)、Map/Set。

写 JSON 出错多半因为这五条——遇到 SyntaxError 时先检查这几项。

3. JSON.stringify 高级用法

// JSON.stringify 高级用法

const user = {
  name: "小明",
  age: 20,
  password: "123456",        // 敏感字段
  birthday: new Date("2000-01-01"),
  sayHi() { console.log("hi"); }  // 函数会被忽略
};

// 1. 第二个参数:白名单数组(只输出指定字段)
const safe = JSON.stringify(user, ["name", "age"]);
console.log(safe);   // {"name":"小明","age":20}

// 2. 第二个参数:回调(过滤/转换)
const filtered = JSON.stringify(user, (key, value) => {
  if (key === "password") return undefined;   // 过滤密码
  return value;
});
console.log(filtered);   // password 字段没了

// 3. 第三个参数:格式化缩进(便于阅读)
const pretty = JSON.stringify(user, null, 2);
console.log(pretty);
// {
//   "name": "小明",
//   "age": 20,
//   ...
// }

// 4. 自定义 toJSON 方法
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  toJSON() {
    return { name: this.name };   // 只序列化 name
  }
}
console.log(JSON.stringify(new User("小红", 22)));
// {"name":"小红"}

三个实用参数组合:

自定义 toJSON:对象可以定义 toJSON() 方法,控制序列化行为——类似 toString()。Date 对象就是这么实现的(返回 ISO 字符串)。

4. JSON.parse 高级用法

// JSON.parse 高级用法

const json = '{"name":"小明","birthday":"2000-01-01T00:00:00.000Z"}';

// 1. 第二个参数:reviver(转换/恢复)
const user = JSON.parse(json, (key, value) => {
  if (key === "birthday") {
    return new Date(value);    // 把日期字符串变回 Date 对象
  }
  return value;
});
console.log(user.birthday.getFullYear());   // 2000

// 2. 解析失败会抛 SyntaxError,必须 try/catch
try {
  const data = JSON.parse(someApiString);
} catch (err) {
  console.error("JSON 格式错误:", err.message);
}

// 3. 安全解析工具(失败时返回默认值,不报错)
function safeParse(str, fallback = null) {
  try {
    return JSON.parse(str);
  } catch {
    return fallback;
  }
}

const data = safeParse('{"a":1}', {});   // 解析成功
const bad = safeParse('invalid', {});     // 失败,返回 {}

reviver(恢复函数):反序列化时转换特定字段。最常见用途是把日期字符串变回 Date 对象(因为 JSON 序列化时 Date 会变字符串)。

务必 try/catch:JSON.parse 解析失败会抛 SyntaxError,如果直接用 localStorage.getItem('xx') 拿到的 null 去 parse,会直接崩。永远包裹在 try/catch 里,或者用上面的 safeParse 工具。

5. 用 JSON 做深拷贝

利用"序列化 → 反序列化"会创建全新对象树的特性,可以实现简单深拷贝:

// JSON 实现"简单深拷贝"
// 利用了 序列化 → 反序列化 时,会创建全新的对象树

const original = {
  name: "小明",
  info: { age: 20, hobbies: ["读书", "写代码"] },
  scores: [90, 85, 92]
};

const copy = JSON.parse(JSON.stringify(original));
copy.info.hobbies.push("游泳");
copy.scores[0] = 100;

console.log(original.info.hobbies);   // ["读书","写代码"](原对象不受影响)
console.log(original.scores[0]);      // 90

// ⚠️ 局限(重要!):
// 1. 函数会被丢弃
// 2. undefined / Symbol 会被丢弃
// 3. Date 变成 ISO 字符串(不是 Date 对象)
// 4. NaN / Infinity / -Infinity 变成 null
// 5. 循环引用会抛错
// 6. Map / Set 会变成 {}

// 循环引用报错示例:
const obj = {};
obj.self = obj;   // 自己引用自己
// JSON.stringify(obj);   // TypeError: Converting circular structure to JSON

局限性——这是为什么"JSON 深拷贝"不能用在所有场景:

纯数据(没有函数、Date、Map)的简单场景可以用 JSON 深拷贝;复杂场景用 structuredClone()(现代浏览器内置)或 lodash 的 _.cloneDeep()

6. JSON 的真实用途

// JSON 的典型用途

// 1. 网络请求(API 都是 JSON)
async function loadUsers() {
  const res = await fetch("/api/users");
  const data = await res.json();    // res.json() 内部就是 JSON.parse
  console.log(data);
}

// 发送 POST 请求时,body 要 JSON 字符串
async function createUser(user) {
  const res = await fetch("/api/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(user)        // 对象 → 字符串
  });
}

// 2. 本地存储(localStorage 只能存字符串)
const settings = { theme: "dark", lang: "zh" };
localStorage.setItem("settings", JSON.stringify(settings));

const loaded = JSON.parse(localStorage.getItem("settings") || "{}");
console.log(loaded.theme);   // dark

// 3. 配置文件(.json / package.json)
// package.json 长这样:
// {
//   "name": "my-app",
//   "version": "1.0.0",
//   "dependencies": { ... }
// }

// 4. 深拷贝(简单场景)
const cloned = JSON.parse(JSON.stringify(original));

四大用途:

  1. 网络请求:REST API、GraphQL 都是 JSON。fetch().then(r => r.json()) 是最常用组合。
  2. 本地存储:localStorage/sessionStorage 只能存字符串,存对象必须 JSON 序列化。
  3. 配置文件:package.json、tsconfig.json 都是 JSON。Node.js 14+ 支持 .jsonc 带注释,但标准 JSON 不允许注释。
  4. 简单深拷贝:有局限,见上节。

7. 实战:封装请求函数

// 封装一个统一处理 JSON 的请求函数
async function request(url, options = {}) {
  // 默认配置
  const config = {
    headers: { "Content-Type": "application/json" },
    ...options
  };

  // 如果有 body,序列化为 JSON
  if (config.body && typeof config.body === "object") {
    config.body = JSON.stringify(config.body);
  }

  try {
    const res = await fetch(url, config);

    // 检查 HTTP 状态
    if (!res.ok) {
      const errText = await res.text();
      throw new Error(\`HTTP ${res.status}: ${errText}\`);
    }

    // 解析 JSON(可能是 204 No Content)
    const text = await res.text();
    return text ? JSON.parse(text) : null;
  } catch (err) {
    console.error("请求失败:", err);
    throw err;   // 重新抛出,让调用方处理
  }
}

// 用法
const user = await request("/api/users/1");
const created = await request("/api/users", {
  method: "POST",
  body: { name: "小明", age: 20 }   // 自动序列化
});

8. 实战:本地存储管理

// 封装一个支持对象的 localStorage 工具
const store = {
  get(key, fallback = null) {
    try {
      const raw = localStorage.getItem(key);
      return raw ? JSON.parse(raw) : fallback;
    } catch {
      return fallback;
    }
  },

  set(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch (err) {
      // 容量满了(QuotaExceededError)
      console.error("存储失败:", err);
      return false;
    }
  },

  remove(key) {
    localStorage.removeItem(key);
  }
};

// 使用
store.set("user", { name: "小明", age: 20 });
const user = store.get("user", {});   // 拿到对象
const theme = store.get("theme", "light");   // 不存在时给默认值

9. 常见错误与排查

小结

恭喜你完成了整个 JavaScript 系列!这是通往前端 / 全栈 / Node.js 世界的钥匙。下一步可以学:

祝你在编程之路上越走越远!

← 上一篇 JavaScript ES6+ 新特性

← 返回 JavaScript 教程目录

✈️💬