JavaScript 教程 · 第七章

JSON 必修课

这一章讲的东西,你天天都在用:你的 Worker、Surge 脚本、各种接口,传来传去的数据几乎都是 JSON。学完这一章,你就能自己把数据「打包成文字发出去、收到文字拆开来用」。

1

这一章学什么

一句话目标:会用 JSON.stringify 和 JSON.parse 在「对象」和「文字」之间互相转换;知道 JSON 的严格规矩;知道 parse 遇到坏数据会直接抛错这个坑,解析之前要防着它。

先把两个词说清楚

  • 对象:程序里能直接用的数据,长这样 { a: 1 },你能用 .a 取里面的值。
  • JSON 字符串:把对象「写成文字」的样子,长这样 '{"a":1}'。它只是一段文字,不能直接用 .a 取值,必须先变回对象。
  • 为什么要变来变去?因为网络传输、存进 KV,都只能传文字,不能直接传对象。
2

概念:JSON 是什么

JSON 是全世界接口传数据的统一格式。不管对方是什么语言写的程序,大家都按这个格式写文字,就都能看懂。

它长得和 JS 对象很像,但规矩比 JS 严得多。写错一点,对方就解析不了:

JS 对象(宽松)

  • 键可以不加引号 可以
  • 字符串单引号、双引号都行 可以
  • 最后多一个逗号 可以

JSON(严格)

  • 键必须用双引号包住 没商量
  • 字符串只能用双引号,单引号不行
  • 不能有尾随逗号(最后一项后面不能多逗号)

记住一句:JSON 是「写给机器看的文字」,不是代码,所以它不许随便。

法 1

对象 → JSON 字符串:JSON.stringify

什么时候用:要把数据发出去、存起来之前。把对象打包成文字。

示例代码
const text = JSON.stringify({ a: 1, b: [1, 2] });
console.log(text);
// 结果是文字:'{"a":1,"b":[1,2]}'
// 注意:键 a、b 都被自动加上了双引号
法 2

JSON 字符串 → 对象:JSON.parse

什么时候用:收到数据后的第一件事。把文字拆回对象,才能取里面的字段。

示例代码
const obj = JSON.parse(text);
console.log(obj.b[0]);
// text 是上面那段文字,parse 之后变回对象
// b 是 [1, 2],取第 0 个,结果是 1
法 3

美化输出:JSON.stringify(obj, null, 2)

默认打包出来的文字是挤成一行的,自己看很难受。加上 null, 2 两个参数,就会换行 + 缩进 2 格,调试看数据时特别好用。

示例代码
const pretty = JSON.stringify(obj, null, 2);
console.log(pretty);
// 输出会变成一行一行、带缩进的样子:
// {
//   "a": 1,
//   "b": [
//     1,
//     2
//   ]
// }

⚠ 本章最大的坑:parse 会「抛错崩掉」

JSON.parse 遇到格式不对的字符串,不会返回个错误提示,而是直接抛错——程序到这一行就崩了,后面的代码全不执行。

所以规矩是:解析外部来的数据(接口返回、响应体这种你控制不了的东西),必须把 parse 包在 try/catch 里面,崩了也能接住、走备用处理:

安全写法
try {
  const obj = JSON.parse(text); // 解析外部数据
  console.log("解析成功", obj);
} catch (e) {
  console.log("解析失败:" + e.message); // 崩了在这里接住
}

还有一条:响应体是不是 JSON 没把握时,先判断再 parse。比如先看看开头是不是 {,或者接口有没有说明返回类型,别上来就 parse 一段网页文字。

3

动手试一试

下面 4 段代码都能真的运行:点「运行」,就在这页里执行,结果显示在代码下面。建议按顺序一个一个点。

1

对象变文字:看键被加上双引号

运行 JSON.stringify({a: 1, b: [1, 2]}),注意输出里 a、b 都变成了 "a"、"b"。

可运行代码
const obj = { a: 1, b: [1, 2] };
const text = JSON.stringify(obj);
console.log(text);
console.log("类型是:" + typeof text);
预期看到:{"a":1,"b":[1,2]},类型是 string(字符串)
2

文字变对象:parse 之后取 .a

运行 JSON.parse('{"a":1}') 再取 .a,看拿到的 1 是数字,不是文字。

可运行代码
const text = '{"a":1}';
const obj = JSON.parse(text);
console.log(obj.a);
console.log("类型是:" + typeof obj.a);
预期看到:1,类型是 number(数字)
3

故意解析坏数据:看抛错被接住

"不是 JSON" 根本不是 JSON 格式,parse 它必然抛错。用 try/catch 包住,错误会被接住,程序不会崩。

可运行代码
try {
  JSON.parse("不是 JSON");
  console.log("解析成功");
} catch (e) {
  console.log("解析失败:" + e.message);
}
预期看到:「解析失败:…」——错误被 catch 接住了
4

美化输出:调试时一眼看清结构

同一份数据,加上 null, 2 后换行缩进,对比一下第 1 个练习里挤成一行的样子。

可运行代码
const obj = { name: "小明", age: 18, tags: ["学习", "JS"] };
const pretty = JSON.stringify(obj, null, 2);
console.log(pretty);
预期看到:一行一行、带缩进的漂亮输出
4

练一练

已答 0 / 5 题 · 答对 0 题
5

适用案例:你自己的工具里天天在用

案例一 · Surge 脚本改响应

改接口返回的数据:先拆开,改完再打包交回去

Surge 脚本里拿到的 $response.body 是一段 JSON 文字,不能直接改字段。固定套路就三步:

  1. JSON.parse 把 body 拆成对象;
  2. 改对象里的字段,想改哪个改哪个;
  3. JSON.stringify 打包回文字,交还给 Surge。
Surge 脚本片段
let obj = JSON.parse($response.body); // 第 1 步:文字 → 对象
obj.vip = true;                        // 第 2 步:改字段
$done({ body: JSON.stringify(obj) });  // 第 3 步:对象 → 文字,交回去

少了第 1 步就改不动字段;少了第 3 步,交回去的就不是文字,Surge 不认。

案例二 · Worker 返回 JSON

你的 Worker 给别人回数据:一行搞定

Worker 的 Response(响应)装的也是文字。所以把对象 stringify 之后塞进去,对方收到的就是标准 JSON:

Worker 代码片段
return new Response(JSON.stringify({ ok: true }), {
  headers: { "content-type": "application/json" }
});
// 对方收到的是文字:'{"ok":true}'
// 对方再用 JSON.parse 就能取到 ok 这个字段
案例三 · KV 存数据

往 KV 里存对象:存之前打包,读出来拆包

KV 里只能存字符串,存不进对象。所以你的 tg-bot、MiSub 这些用到 KV 的地方,全都是这个套路:

KV 读写片段
// 存:对象先 stringify 成文字,才能放进 KV
await KV.put("user", JSON.stringify({ count: 3 }));

// 读:拿出来的是文字,先 parse 回对象再用
const text = await KV.get("user");
const user = JSON.parse(text);
console.log(user.count); // 3

忘了 parse 就直接 text.count,只会得到 undefined(取不到)——这是新手最常踩的坑之一。

一句话小结

发出去、存起来之前用 JSON.stringify 打包成文字;收到数据先用 JSON.parse 拆回对象,拆外部数据时记得用 try/catch 防它抛错。

第七章完 · 点过「运行」、做完 5 道题,这一章就算过关了