JSON 解析与序列化完整指南
覆盖 JavaScript、Python、Java、Go 四大主流语言的 JSON 处理方法,以及常见陷阱、性能优化和最佳实践。
一、JSON 解析的核心概念
JSON 处理只有两个核心操作:解析(Parse)—— 将 JSON 字符串转换为编程语言中的数据结构(对象、字典、Map 等);以及 序列化(Serialize/Stringify)—— 将内存中的数据结构转换为 JSON 字符串。理解这两个操作是正确处理 JSON 的前提。
每个语言对这两个操作的命名不同,但本质完全一致:
| 语言 | 解析(字符串→对象) | 序列化(对象→字符串) |
|---|---|---|
| JavaScript | JSON.parse(str) | JSON.stringify(obj) |
| Python | json.loads(str) | json.dumps(obj) |
| Java | new ObjectMapper().readValue(str, Class) | new ObjectMapper().writeValueAsString(obj) |
| Go | json.Unmarshal([]byte, &target) | json.Marshal(obj) |
二、JavaScript:JSON.parse 的陷阱
陷阱 1:单引号 JSON 导致 SyntaxError
这是新手最常见的错误。JavaScript 对象字面量可以用单引号,但 JSON.parse 严格要求双引号:
// 错误
JSON.parse("{'name': 'Alice'}") // SyntaxError!
// 正确
JSON.parse('{"name": "Alice"}')陷阱 2:大整数精度丢失
JavaScript 的 Number 类型基于 IEEE 754 双精度浮点数,最大安全整数为 253-1(9007199254740991)。超过此值的整数会丢失精度:
const json = '{"id": 9007199254740993}';
const obj = JSON.parse(json);
console.log(obj.id); // 9007199254740992 — 丢失了一位!正确做法:在序列化时,将大整数转为字符串传输;或使用 BigInt + 自定义 reviver。
陷阱 3:undefined、Function、Symbol 无法序列化
JSON.stringify 会自动忽略值为 undefined、Function、Symbol 的属性:
JSON.stringify({ a: undefined, b: () => {}, c: 42 })
// 结果: '{"c":42}' — a 和 b 被静默丢弃reviver 参数的高级用法
JSON.parse 的第二个参数 reviver 允许在处理过程中转换值。典型场景:将日期字符串自动转为 Date 对象。
const data = JSON.parse('{"createdAt":"2024-01-15T08:30:00Z"}', (key, val) =>
key === 'createdAt' ? new Date(val) : val
);
console.log(data.createdAt instanceof Date); // true三、Python:json 模块的注意事项
datetime 对象的序列化
Python 的 datetime 对象不能被 json.dumps 直接序列化。解决方案是自定义 encoder:
import json
from datetime import datetime
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
data = {"event": "login", "time": datetime.now()}
json.dumps(data, cls=DateTimeEncoder)使用 json.load 读取大文件
json.loads 一次性将整个文件读入内存。对于大文件(>100MB),应使用流式解析库如 ijson:
import ijson
with open('large_file.json', 'r') as f:
for item in ijson.items(f, 'users.item'):
process(item) # 逐个处理,内存占用极低四、JSON 解析性能优化
核心原则:JSON 解析的性能瓶颈不是 CPU 而是内存分配。减少中间对象的创建是优化的关键。
- 预分配容量:在 Java 中,如果已知 JSON 数组的大致长度,预先设置 ArrayList 的初始容量可减少数组扩容次数。
- 流式解析:对于超大 JSON 文件,避免一次性加载。使用 SAX 风格的流式解析器(如 Jackson Streaming API、Gson 的 JsonReader)。
- 选择正确的库:性能敏感场景下,不同库的差距可达 5-10 倍。Java 生态中 Jackson 通常比 Gson 快;Go 生态中
jsoniter比标准库快;JavaScript 中直接使用原生 API 通常最可靠。 - 重用 ObjectMapper:在 Java 中,Jackson 的 ObjectMapper 实例是线程安全的,应作为单例复用而非每次解析新创建一个。
- 避免深层嵌套:嵌套深度每增加一层,解析时间呈非线性增长。尽量保持 JSON 数据结构的扁平化(建议不超过 10 层)。
五、JSON 解析安全性
永远不要信任用户提供的 JSON。以下是常见攻击向量:
- 炸弹 JSON(Billion Laughs 风格):极深的嵌套或极长的字符串可导致解析器耗尽内存。为解析器设置最大深度限制(如 100 层)和最大字符串长度限制。
- 原型污染(仅限 JavaScript):如果使用
Object.assign或展开运算符合并不受信 JSON,{"__proto__": {"isAdmin": true}} 这类载荷可能污染原型链。始终使用Object.create(null)或 Map 存储不可信数据。 - SQL/NoSQL 注入:解析后的值若直接拼入查询语句,攻击者可在 JSON 值中嵌入注入载荷。始终使用参数化查询。
六、跨语言 JSON 解析快速参考
| 操作 | JavaScript | Python | Java (Jackson) | Go |
|---|---|---|---|---|
| 解析字符串 | JSON.parse(s) | json.loads(s) | mapper.readValue(s, T.class) | json.Unmarshal(b, &t) |
| 解析文件 | fetch().then(r=>r.json()) | json.load(f) | mapper.readValue(f, T.class) | json.NewDecoder(f).Decode(&t) |
| 序列化 | JSON.stringify(o) | json.dumps(o) | mapper.writeValueAsString(o) | json.Marshal(o) |
| 美化输出 | JSON.stringify(o,null,2) | json.dumps(o,indent=2) | mapper.writerWithDefaultPrettyPrinter() | json.MarshalIndent(o,""," ") |
| 自定义键名 | 使用 reviver | @dataclass / dict | @JsonProperty("name") | json:"name" tag |
| 忽略未知字段 | 默认忽略 | 需自定义 hook | @JsonIgnoreProperties | 默认忽略 |
准备好实践 JSON 解析技巧了吗?
打开 JSON 格式化工具