JSON 解析与序列化完整指南

覆盖 JavaScript、Python、Java、Go 四大主流语言的 JSON 处理方法,以及常见陷阱、性能优化和最佳实践。

一、JSON 解析的核心概念

JSON 处理只有两个核心操作:解析(Parse)—— 将 JSON 字符串转换为编程语言中的数据结构(对象、字典、Map 等);以及 序列化(Serialize/Stringify)—— 将内存中的数据结构转换为 JSON 字符串。理解这两个操作是正确处理 JSON 的前提。

每个语言对这两个操作的命名不同,但本质完全一致:

语言解析(字符串→对象)序列化(对象→字符串)
JavaScriptJSON.parse(str)JSON.stringify(obj)
Pythonjson.loads(str)json.dumps(obj)
Javanew ObjectMapper().readValue(str, Class)new ObjectMapper().writeValueAsString(obj)
Gojson.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 会自动忽略值为 undefinedFunctionSymbol 的属性:

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 解析快速参考

操作JavaScriptPythonJava (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 格式化工具