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记住两个核心方法:
JSON.stringify(value):JS 值 → JSON 字符串(序列化,发送/存储时用)。JSON.parse(text):JSON 字符串 → JS 值(反序列化,接收时用)。
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(会转字符串)、RegExpJSON 的五大禁忌:
- 键必须用双引号(不是单引号,不能省略)。
- 字符串值必须用双引号。
- 不能有尾随逗号(最后一个属性后多写逗号会报错)。
- 不能有注释。
- 只支持有限类型:数字、字符串、布尔、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":"小红"}三个实用参数组合:
- 白名单数组:只输出指定字段(过滤敏感数据)。
- 回调函数:更精细的转换逻辑。
- 缩进数字:
2表示 2 空格缩进,生成的 JSON 美观易读(配置文件常用)。
自定义 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 深拷贝"不能用在所有场景:
- 函数、undefined、Symbol 会被丢弃。
- Date 变成字符串(不是 Date 对象)。
- NaN/Infinity 变成 null。
- 循环引用会报错(
obj.self = obj)。 - Map/Set 变成
{}。
纯数据(没有函数、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));四大用途:
- 网络请求:REST API、GraphQL 都是 JSON。
fetch().then(r => r.json())是最常用组合。 - 本地存储:localStorage/sessionStorage 只能存字符串,存对象必须 JSON 序列化。
- 配置文件:package.json、tsconfig.json 都是 JSON。Node.js 14+ 支持 .jsonc 带注释,但标准 JSON 不允许注释。
- 简单深拷贝:有局限,见上节。
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. 常见错误与排查
- Unexpected token:JSON 字符串里有非法字符(单引号、尾逗号、注释)。把字符串粘到 JSONLint 这种工具里检查。
- Unexpected end of JSON input:字符串是空的,parse 之前先判空。
- Converting circular structure:对象循环引用,JSON.stringify 无法处理。
- 序列化后 Date 变字符串:用 toJSON 或 reviver 处理。
- parse 后函数丢失:JSON 不支持函数,只能存数据,函数代码需要单独保留。
小结
- JSON 是跨语言的数据交换格式,本质是 JS 对象的字符串表示。
- 核心方法:
JSON.stringify()(序列化)和JSON.parse()(解析)。 - JSON 比对象字面量更严格:双引号、无尾逗号、无注释、类型有限。
- 用于网络请求、本地存储、配置文件、深拷贝(有局限)。
- 解析时务必 try/catch,避免 JSON 格式错误导致程序崩溃。
恭喜你完成了整个 JavaScript 系列!这是通往前端 / 全栈 / Node.js 世界的钥匙。下一步可以学:
- TypeScript:带类型的 JS,大型项目首选。
- React / Vue:前端框架,真正构建现代应用。
- Node.js:JS 写后端,前后端通吃。
- Canvas / WebGL:绘图、游戏、可视化。
祝你在编程之路上越走越远!
← 上一篇 JavaScript ES6+ 新特性
← 返回 JavaScript 教程目录