JSON解析与序列化完全指南:从原理到实战,让数据流动自如
凌晨两点,你盯着控制台里那个红得刺眼的报错:
SyntaxError: Unexpected token < in JSON at position 0
又来了。接口返回的不是JSON,是一段HTML错误页。你习以为常地复制粘贴,祈祷这次能跑通。
这就是大多数开发者和JSON的关系——每天用,但从来没真正搞懂过它。别担心,这篇文章让你从「会用」升级到「用得明白」。
一、JSON是什么?为什么开发者离不开它
JSON(JavaScript Object Notation)是一种轻量级数据交换格式。它不是JavaScript的专属,而是几乎所有语言都能生成和解析的通用格式。你调API、存配置、做微服务通信——JSON无处不在。
它的核心优势:
- 人类可读,调试友好
- 结构简洁,序列化后体积小
- 语言无关,跨平台互通
- 几乎所有编程语言都有原生或库级支持
简单说:JSON就是程序员的事实标准。搞懂它,你的效率会翻倍。
二、解析(Parsing):把字符串变成对象
JavaScript:JSON.parse()
最常见的场景:拿到一段JSON字符串,解析成JavaScript对象。
const jsonStr = '{"name": "Alice", "age": 25, "skills": ["Python", "Go"]}';
const obj = JSON.parse(jsonStr);
console.log(obj.name); // "Alice"
console.log(obj.skills[0]); // "Python"
看起来简单,但有三个坑你必须知道:
坑1:严格模式,不允许尾部逗号
// 这会报错
JSON.parse('{"name": "Bob",}'); // SyntaxError: Unexpected token }
// 正确写法
JSON.parse('{"name": "Bob"}');
坑2:属性名必须用双引号
// 单引号 → 报错
JSON.parse("{'name': 'Carol'}"); // SyntaxError: Unexpected token '
// 正确
JSON.parse('{"name": "Carol"}');
坑3:undefined和函数会被忽略
const obj = {
name: "Dave",
age: undefined, // 被忽略
greet: function() { return "hi"; }, // 被忽略
active: true
};
const jsonStr = JSON.stringify(obj);
console.log(jsonStr); // {"name":"Dave","active":true}
console.log(JSON.parse(jsonStr).age); // undefined
Python:json.loads()
Python中解析JSON用的是json模块的loads()函数(load string)。
import json
json_str = '{"name": "Alice", "age": 25, "skills": ["Python", "Go"]}'
obj = json.loads(json_str)
print(obj["name"]) # Alice
print(obj["skills"][0]) # Python
Python的坑主要在于类型映射:JSON的true/false变成Python的True/False,null变成None。如果你习惯用None做判断,别忘了这一层转换。
三、序列化(Serialization):把对象变成字符串
JavaScript:JSON.stringify()
序列化是将对象转换为JSON字符串的过程。
const obj = { name: "Eve", score: 98.5, passed: true };
const jsonStr = JSON.stringify(obj);
console.log(jsonStr);
// {"name":"Eve","score":98.5,"passed":true}
JSON.stringify()还支持两个额外的参数:
第二个参数:过滤器和格式化
const obj = { name: "Frank", age: 30, password: "secret123" };
// 只保留指定字段
const filtered = JSON.stringify(obj, ["name", "age"]);
console.log(filtered); // {"name":"Frank","age":30}
// 用函数自定义处理
const custom = JSON.stringify(obj, (key, value) => {
if (key === "password") return undefined; // 排除密码字段
return value;
});
console.log(custom); // {"name":"Frank","age":30}
第三个参数:缩进美化
const obj = { name: "Grace", city: "Beijing" };
// 用空格缩进(2个空格)
console.log(JSON.stringify(obj, null, 2));
// {
// "name": "Grace",
// "city": "Beijing"
// }
// 用制表符缩进
console.log(JSON.stringify(obj, null, "\t"));
// {
// "name": "Grace",
// "city": "Beijing"
// }
注意:格式化输出只适合调试和日志,生产环境传输应使用无缩进的最小化格式,否则徒增网络开销。
Python:json.dumps()
import json
obj = {"name": "Grace", "city": "Beijing", "score": 98.5}
json_str = json.dumps(obj)
print(json_str) # {"name": "Grace", "city": "Beijing", "score": 98.5}
# 美化输出(indent参数)
print(json.dumps(obj, indent=2, ensure_ascii=False))
# {
# "name": "Grace",
# "city": "Beijing",
# "score": 98.5
# }
# 排除某个字段
obj.pop("score", None)
print(json.dumps(obj)) # {"name": "Grace", "city": "Beijing"}
ensure_ascii=False在处理中文时是必须的,否则中文会被转义为\u4e2d...的形式,可读性大幅下降。
四、实战技巧:处理复杂场景
1. 深拷贝:比Object.assign更安全
// Object.assign是浅拷贝,嵌套对象会共享引用
const original = { user: { name: "Henry", scores: [90, 85] } };
const shallow = Object.assign({}, original);
shallow.user.name = "Hank";
console.log(original.user.name); // "Hank" — 原对象被改了!
// JSON.stringify + JSON.parse 实现深拷贝
const deep = JSON.parse(JSON.stringify(original));
deep.user.name = "Hank";
console.log(original.user.name); // "Henry" — 安全
注意:这种方法无法拷贝函数、undefined、Symbol等特殊值。如果你的对象包含这些类型,需要用专门的深拷贝库(如lodash的cloneDeep)。
2. 处理日期对象
// 日期对象序列化后变成字符串,反序列化后还是字符串
const event = { title: "Conference", date: new Date("2026-05-10") };
const str = JSON.stringify(event);
console.log(str); // {"title":"Conference","date":"2026-05-10T00:00:00.000Z"}
const parsed = JSON.parse(str);
console.log(parsed.date); // "2026-05-10T00:00:00.000Z" — 是字符串,不是Date对象
// 如需还原为Date对象,自定义reviver函数
const restored = JSON.parse(str, (key, value) => {
if (key === "date") return new Date(value);
return value;
});
console.log(restored.date instanceof Date); // true
3. 循环引用的处理
// 直接序列化包含循环引用的对象会报错
const obj = { name: "Iris" };
obj.self = obj;
try {
JSON.stringify(obj);
} catch (e) {
console.log(e.message); // "Converting circular structure to JSON"
}
// 解决方案:用 replacer 过滤或使用库
const safeObj = JSON.stringify(obj, (key, value) => {
if (value === obj) return "[Circular]";
return value;
});
console.log(safeObj); // {"name":"Iris","self":"[Circular]"}
4. 大数字精度丢失问题
// JavaScript中Number类型的安全整数范围:-2^53+1 到 2^53-1
// 超过这个范围的数字会丢失精度
const bigNum = { id: 9007199254740993 }; // 2^53+1
console.log(JSON.parse(JSON.stringify(bigNum)).id); // 9007199254740992 — 精度丢失!
// 解决方案:用字符串传递大数字,或使用专门的bigint库
五、常见错误与解决方案
| 错误信息 | 原因 | 解决方式 |
|---|---|---|
Unexpected token in JSON |
字符串包含非法字符或格式错误 | 检查JSON有效性,用工具格式化验证 |
Unexpected end of JSON input |
字符串被截断,或为空 | 检查接口响应是否完整,body是否为空 |
Expected ',' or '}' |
少了逗号或多了逗号 | 逐行检查JSON结构 |
Unexpected character in JSON |
属性名用了单引号或未加引号 | 属性名必须用双引号包裹 |
Converting circular structure to JSON |
对象存在循环引用 | 用replacer过滤或移除循环引用 |
| BOM头导致的解析错误 | UTF-8文件带BOM头 | 保存文件时选择「UTF-8 无BOM」格式 |
| 中文字符变成Unicode转义 | 未设置ensure_ascii=False(Python) |
json.dumps(obj, ensure_ascii=False) |
六、推荐工具:用在线JSON格式化器省时间
手动处理JSON太累了?善用工具能让你事半功倍。
如果你需要一个能实时校验、格式化、美化的JSON工具,我常用 CloverTools JSON Formatter。它能帮你:
- 秒级解析并美化任意JSON字符串
- 快速定位语法错误位置
- 压缩JSON(去除空格,用于生产环境传输)
- 在格式化与压缩之间一键切换
地址:https://clovertools.cn/tools/json/formatter.html
写代码时我习惯浏览器开一个标签页,接口返回的JSON直接丢进去格式化,省去本地跑脚本的麻烦。
七、总结
JSON的解析和序列化看似基础,但细节坑不少:
- 解析时注意格式严格性,双引号、禁止尾部逗号
- 序列化时注意大数字精度、循环引用、日期对象
- 调试时善用在线工具,格式化输出大幅提升可读性
- 生产时使用压缩格式,减少传输体积
把这些细节变成肌肉记忆,下次遇到JSON报错时,你就不用再靠运气了。
工具地址再放一次:https://clovertools.cn/tools/json/formatter.html — 用起来,效率翻倍。
常见问题
A: 这类工具一般有明确的输入框和输出框,按提示输入内容,点击对应按钮即可得到结果。建议先用简单示例测试功能是否正常,再处理实际数据。
A: 根据具体工具类型决定。格式转换工具适合处理第三方数据,编码工具适合加密传输,压缩工具适合文件上传前处理。多积累工具使用经验,遇到问题时能快速判断用哪个工具解决。
A: 不同工具有不同侧重,重点是理解原理。可以同时安装多个类似工具,实际使用中对比效果,选择最顺手的一个。随着使用经验增加,你也能判断工具的好坏。