等了半天,JSON v2 呢?

Go 1.26 发布了,大家翻完 Release Notes 都在问同一个问题:JSON v2 呢?

这个期待不是空穴来风。encoding/json 这个包服务了 Go 社区十多年,是 Go 程序员每天都要打交道的老朋友。但这个老朋友身上攒了不少"坏习惯",社区的吐槽声从来没断过。

所以当 Go 团队宣布要搞一个全新设计的 JSON v2 时,大家的反应基本是:终于来了!

结果 Go 1.26 发布,JSON v2 还是挂在 encoding/json/v2 这个实验性路径下,正式转正的时间表依然是"等通知"。

不是 Go 团队放鸽子。他们做了一个让很多人意外但又不得不佩服的决定:东西还没打磨好之前,绝不强行上线。

v1 的那些槽点

要理解为什么 JSON v2 这么难产,得先看看 v1 到底有多少问题。

把 JSON v1 想象成一个用了十年的老厨房——当初装修的时候觉得挺好用,时间一长,各种设计缺陷就暴露出来了。

JSON v1 的槽点:像一个老旧的厨房

性能问题。 v1 处理 JSON 时,会把整个 JSON 值先完整地缓冲到内存里,再进行解析。这就像每次做菜都要先把所有食材都拿出来摆满整个台面,然后才开始动刀。小规模数据还行,一旦遇到大的 JSON 响应,内存占用就非常不友好。

API 设计问题。 json.NewDecoder(r).Decode(v) 这个写法,看起来很正常对吧?但它不会拒绝 JSON 末尾的垃圾数据。从流里读取 JSON 时,后面多出来的内容会被悄悄忽略掉。处理网络请求时,这可能埋下安全隐患。

// v1 的隐患:不会报错,后面多出的垃圾会被忽略
var data MyStruct
json.NewDecoder(responseBody).Decode(&data)
// 如果 responseBody 是 `{"name":"test"}垃圾数据`
// v1 会成功解析,垃圾数据被悄悄吞掉

行为不一致。 v1 在 unmarshaling 时用的是大小写不敏感的匹配。你的结构体字段叫 UserName,JSON 里写 usernameUSERNAMEUsErNaMe 都能匹配上。听起来很"智能"?这对性能是个打击(不能用简单的 map 查找),而且可能被攻击者利用来绑过安全检查。

还有一个让人头疼的:MarshalJSONUnmarshalJSON 这两个接口方法,在某些情况下根本不会被调用。比如当值是通过 map 或 interface 获取的时候,底层值不可寻址,这些自定义方法就成了摆设。“有时候管用有时候不管用"的行为,调试起来能把人逼疯。

Go 团队自己也承认:v1 的很多行为当初设计时没想清楚,后来成了历史包袱。想改?不敢改。不知道有多少生产环境的代码正在依赖这些"bug 级别"的行为。

v2:推倒重来

面对这些问题,Go 团队做了一个决定:不修修补补了,直接设计一套全新的 JSON 库。

JSON v2 的设计理念:宁可 API 不 100% 兼容,也要把事情做对。

整个 v2 的架构分成了两层。jsontext 负责纯粹的 JSON 语法处理——编码、解码、token 解析。这层不依赖 Go 的反射机制,体积很小,适合对二进制大小敏感的场景(TinyGo、WebAssembly)。

第二层才是我们熟悉的 json 包,负责把 Go 类型和 JSON 互相转换。它依赖 jsontextreflect,提供日常使用的 MarshalUnmarshal 功能。

这就像把一家"大而全"的超市拆成了"菜市场和厨房用品店”——想买生鲜的去菜市场,想买锅碗瓢盆的去厨具店。

JSON v2 分层架构:jsontext 和 json 包

性能方面,v2 承诺做到真正的流式处理。从 io.Reader 读 JSON 的时候,只缓冲当前正在处理的那个 token,而不是把整个 JSON 都吞进去。大文件解析时,内存占用能降低一个数量级。

// v2 的流式处理示例
import "encoding/json/v2"

// 真正的流式读取,只缓冲当前 token
dec := json.NewDecoder(reader)
for dec.PeekKind() > 0 {
    var item MyStruct
    if err := dec.Decode(&item); err != nil {
        break
    }
    // 处理每条记录,内存占用稳定
}

正确性方面,v2 默认拒绝不合法的 UTF-8 字符,拒绝重复的 JSON 对象键名(v1 里是被允许的,但 RFC 8259 标准说这会导致"未定义行为")。安全优先于性能。

那为什么 Go 1.26 还是不敢把它扶正?

还有坑没填完

性能回归。 v2 的早期测试中,开发者发现了一个严重的 bug:处理某些复杂的对象树或特定的 map 结构时,内存分配的次数减少了,但单次分配的大小暴涨。这会给垃圾回收器带来巨大压力。如果这个问题随着 v1 的默认行为一起发布,后果可能很糟糕。

任务清单没清空。 根据 Go 官方的 Project 50 追踪器,JSON v2 的 44 个子任务中,大约还有 18 个处于开放状态或需要进一步审核(截至 2026 年 2 月)。

JSON v2 任务进度追踪

这些是可能影响 API 设计的关键问题:

  • Union 类型(联合类型)支持:很多开发者认为现代 JSON 处理器应该支持反序列化联合类型,Go 团队的态度是"等一个成熟的方案出现"。
  • 部分字段更新:在只想更新结构体某些字段的场景下,v2 的行为还没完全敲定。
  • 内联和展平:把嵌套的结构体或 map “展平"到父级 JSON 对象,这个功能的 API 还在讨论中。

向后兼容性。 Go 团队的目标是 95% 到 99% 的代码能无缝迁移。那剩下的 1% 到 5% 怎么办?需要提供清晰的迁移路径和工具支持。

Go 团队正在开发一套现代化的 go fix 工具,不只能做简单的字符串替换,而是类型感知的代码转换。比如,它能自动检测你自己写的 Base64 转换逻辑,然后建议换成 v2 的原生实现。这套工具还在打磨中。

标准库不是普通库

Go 团队拒绝带着已知缺陷发布 API。

软件行业里,“先发布再修复"的例子太多了。功能赶着上线,bug 以后再说,技术债务越积越多,最后变成无人敢动的"遗留系统”。

JSON v2 的延迟,说明 Go 团队对待标准库的态度:一旦进入标准库,API 就要稳定十年以上。现在多花几个月甚至一年打磨,比将来背着包袱走十年要划算。

要建一栋住十年的房子,地基没打好之前,别急着装修。

现在能用吗?

能。虽然是实验性的,但已经在很多生产环境中验证过了。

如果 JSON 处理是你的性能瓶颈,或者你对 v1 的某些行为实在受不了,可以试试 encoding/json/v2。只是要记住:

  1. API 还可能有变化,升级时可能需要改代码
  2. 保持关注 Go 1.27 的开发周期,那很可能是 JSON v2 正式转正的时间窗口
  3. 遇到问题,在 Go 的 issue tracker 上反馈,帮助团队发现和修复问题

常见问题 FAQ

JSON v1 vs v2 对比

Q: JSON v2 性能提升多少? A: 官方测试显示,流式处理场景下内存占用可降低一个数量级。具体提升取决于你的数据结构和处理模式。

Q: 现在的 v2 实验版能用于生产吗? A: 可以,但要注意 API 可能有变化。建议在非核心业务先验证,并保持关注版本更新。

Q: v1 和 v2 能同时使用吗? A: 能。v2 的 import 路径是 encoding/json/v2,和 v1 的 encoding/json 不冲突。

Q: 什么时候会正式发布? A: 没有官方时间表。Go 1.27(预计 2026 年 8 月)是一个可能的时间窗口,但取决于剩余任务的完成情况。


速查清单:JSON v1 vs v2 对比

问题v1 行为v2 改进
流式处理缓冲整个 JSON 值真正的流式,只缓冲当前 token
UTF-8 验证允许非法 UTF-8默认拒绝,符合 RFC 8259
重复键名允许默认拒绝,提升安全性
大小写匹配不敏感匹配可配置,默认更严格
自定义方法调用不一致始终调用,行为可预测
末尾垃圾数据静默忽略可选拒绝
依赖需要 reflectjsontext 层无反射依赖

参考资料