本文永久链接 – https://tonybai.com/2026/11/07/why-go-json-v2-took-six-years

大家好,我是Tony Bai。

【导读】

JSON 是互联网世界最普遍的数据交换格式之一,也是 Go 标准库中使用最广泛的组件之一。

但你可能不知道,Go 团队为了重写 encoding/json(即下面多次提到的 JSON v2),前后花费了将近六年时间。

从 2020 年的最初构想到 2023 年的社区讨论,从 2025 年 Go 1.25 的实验性发布,到 2026 年 Go 1.27 正式稳定,encoding/json/v2 终于走完了这段漫长的旅程。

这不是一次简单的性能优化,而是一次涉及 API 设计、历史兼容性、底层架构、社区协作和生态迁移的系统性重构。

Go 团队究竟遇到了什么问题?为什么不能直接修复旧版?六年时间又沉淀出了怎样的工程经验?

【文章要点】

  • 六年马拉松的终点:Go 1.27 正式将 encoding/json/v2 和 encoding/json/jsontext 标记为稳定可用,宣告自 2020 年由 Daniel Martí、Joe Tsai 等社区骨干发起的长周期重构顺利落地;
  • 旧版为何不能直接修:受制于 Go 1 严格的向后兼容承诺,旧版在大小写模糊匹配、nil slice 编码为 null、接受非法 UTF-8 等历史行为已成为既有契约,直接修正会破坏下游海量业务,必须另辟 v2 命名空间;
  • 语法与语义彻底解耦的双层架构:将原本交织的解析逻辑拆解为独立的 jsontext(专注于 JSON 语法、Token 流式处理,脱离 Go 反射依赖)与高层的 v2(专注于 Go 结构体映射、自定义 Marshaler 与可配置 Options),实现模块化独立演进;
  • 共用底层引擎实现平滑共存:旧版 encoding/json 绝不废弃,而是直接由 v2 的底层实现驱动,存量项目无需修改一行代码即可获得反序列化最高达 10 倍的性能收益与持续维护保障;
  • 克制演进的工程课:这不仅是一次序列化库的代码升级,更是大型基础软件生态在面临严重历史包袱时,如何通过“识别问题—设计分层—原型验证—社区迭代—双轨过渡”完成系统性重构的标准范本。


2026 年 11 月 4 日,Go 官方博客发布了一篇新文章:《A new Go API for JSON》。

这篇文章并没有带来什么全新的 API,也不是一次普通的 Go 1.27 特性介绍。它更像是一份迟到了六年的项目总结:Go 团队终于可以正式宣布,encoding/json/v2 和 encoding/json/jsontext 已经在 Go 1.27 中稳定发布。

从 2020 年最初的设计构想到今天,这项工作已经走过了将近六年。

有意思的是,JSON 本身并不复杂。它只有对象、数组、字符串、数字、布尔值和 null 几种基本结构。

那么,为什么一个 JSON 库的重写,竟然需要如此漫长的时间?

答案藏在 Go 标准库的历史包袱、API 设计以及 Go 社区的工程文化之中。

一个不能轻易修改的老牌标准库

Go 对 JSON 的支持,远比很多人想象得更早。

在 Go 1.0 正式发布之前,标准库就已经提供了 encoding/json。十多年过去,它早已成为 Go 生态最重要的基础组件之一。

Go 官方在 2025 年的实验版介绍中提到,encoding/json 已经是 Go 项目中被导入次数排名第五的包。

然而,使用范围越广,重构的代价也就越大。

经过多年发展,旧版 encoding/json 暴露出了一些设计缺陷:

  • 标准合规性不足:接受非法 UTF-8 数据,也允许 JSON 对象出现重复字段名。
  • 默认行为不够合理:nil slice 和 nil map 会被编码成 null,而不是空数组和空对象。
  • 字段匹配过于宽松:反序列化时采用大小写不敏感匹配。
  • API 扩展受限:自定义 MarshalJSON 和 UnmarshalJSON 方法难以获得调用方配置的选项。
  • 流式处理能力不足:传统接口需要先构造完整的 JSON 字节切片,难以真正做到高效流式处理。

这些问题并不是最近才被发现的。

事实上,Go 社区早已围绕它们讨论多年。

那么,为什么不直接修复?

这里涉及 Go 最重要的工程原则之一:Go 1 兼容性承诺。

一个被大量项目依赖的标准库,其既有行为已经成为事实上的 API 契约。

例如,旧版允许大小写不敏感匹配。如果突然改成严格匹配,可能导致某些一直正常运行的业务系统无法解析原有数据。

同样,nil slice 从 null 改成 [],虽然在很多场景下更加合理,却依然会改变程序输出。

这些改变可能是正确的,但不能简单地塞进一个已有十多年历史的 API 中。

Go 团队最终意识到:有些问题不是修复几个 Bug 就能解决的,而是需要重新设计 API。

于是,JSON v2 诞生了。

从 2020 到 2026:一场持续六年的重构

这项工作的历程,实际上比 Go 1.25 实验版发布要早得多。

我们可以从 Go 官方博客披露的信息中,还原出一条相对完整的时间线。

这条时间线里,有两个值得特别关注的人物。

Daniel Martí 是 encoding/json 的维护者之一。他最早从旧版 API 的设计局限出发,思考如何构建一个全新的版本。

Joe Tsai 则有另一条技术线索。在参与 Go Protobuf API 的工作后,他发现 protojson 不得不使用独立的 JSON 实现,因为旧版 encoding/json 无法同时满足更严格的 JSON 标准要求和高效流式处理需求。

这成为推动 JSON v2 的另一个重要契机。

两人最终走到一起,开始构建新的 JSON 实现。

随后,Roger Peppe、Chris Hines、Johan Brandhorst-Satzkorn、Damien Neil、Austin Clements、Michael Pratt 等人陆续参与设计评审、代码审查和回归测试。

值得注意的是,Go 官方特别强调,这项工作很大程度上由 Google 之外的开发者推动和贡献。

这也是 Go 开源项目协作模式的一个缩影。

真正的重构:把 JSON 处理拆成两个层次

如果说前两部分讲的是为什么要重写,以及重写经历了什么,那么这一部分才是 JSON v2 最重要的技术成果。

过去,很多开发者会把 JSON 序列化理解成一个简单的过程:

Go 对象转换成 JSON 字符串,或者反过来。

但从实现角度来看,这里面其实包含两个不同的问题:

  • JSON 本身是否合法?如何解析 JSON 语法?
  • JSON 数据应该如何映射到 Go 类型?

旧版 encoding/json 将这两部分逻辑交织在一起,导致 API 扩展、性能优化和行为调整都受到限制。

JSON v2 最重要的架构调整,就是将它们彻底分离。

第一层:encoding/json/jsontext

负责 JSON 语法层面的处理。

它不关心 Go 的 struct、反射或者类型映射,只负责 JSON 本身。

例如:

  • 读取和写入 JSON Token。
  • 解析和生成完整 JSON Value。
  • 处理 JSON 编码、解码。
  • 提供真正的流式处理能力。

第二层:encoding/json/v2

负责 JSON 与 Go 类型之间的语义映射。

例如:

  • Go struct 如何映射到 JSON Object。
  • Go slice 如何映射到 JSON Array。
  • 如何处理自定义 Marshaler。
  • 如何通过 Options 控制序列化行为。

两层架构如下:

图:JSON v2 的双层架构

这种分层带来的价值远不止代码结构更加清晰。

首先,语法层不再依赖 Go 的反射机制,可以作为独立的 JSON 处理组件使用。

其次,语义层不再需要自己承担所有底层 JSON 解析工作,能够更加灵活地实现类型映射。

更重要的是,双方可以独立演进。

这也是为什么 JSON v2 不仅仅是一次代码重写,而是一次底层架构的重新设计。

六年重构,还有一个更难的任务:兼容过去

对于一个新项目来说,重新设计 API 并不困难。

真正困难的是:如何让新 API 和旧 API 在同一个生态中长期共存?

Go 团队在 JSON v2 设计中做了一个非常关键的决定:

旧版 encoding/json 并不会被废弃,而是使用新版实现作为底层引擎。

换句话说,encoding/json 和 encoding/json/v2 并不是两套完全独立的 JSON 实现。

底层统一,行为分流。

图:新旧 API 共用底层实现

这样设计有几个好处:

第一,避免维护两套实现。

未来修复底层 Bug、优化性能或者增加新能力,不需要分别维护两套 JSON 引擎。

第二,允许逐步迁移。

开发者可以通过 Options 选择不同的行为组合,不必一次性接受所有行为变化。

第三,让旧版也能受益。

例如,新版提供了 MarshalJSONTo 和 UnmarshalJSONFrom 接口。即使某个项目仍然通过 v1 调用 JSON 序列化,也可以在底层使用这些新接口。

这意味着,Go 团队没有要求整个生态立即迁移到 v2,而是尽可能降低迁移成本。

官方也明确表示:旧版 encoding/json 将继续得到支持,不会因为 v2 的发布而被废弃。

这其实是一个非常值得借鉴的工程决策。

重构基础设施,不应该把迁移成本简单地转嫁给使用者。

小结:六年之后,Go 团队交付的不只是一个 JSON 库

Go 1.27 中的 JSON v2,终于从实验性功能变成了正式标准库 API。

从结果来看,它解决了旧版长期存在的设计问题,也带来了更灵活的 API 和更好的性能表现。

根据官方公布的基准测试,v2 的 Marshal 性能总体与 v1 接近,而 Unmarshal 在部分场景下最高可以达到 10 倍性能提升。

不过,Go 团队也没有回避性能上的不足:部分场景仍然慢于 v1,后续 Go 1.28 还会继续优化。

对于开发者而言,现在并不需要急着把所有项目迁移到 v2。

Go 官方已经提供了专门的迁移指南,旧版 API 也会继续得到支持。

真正值得关注的,是这项工作背后的工程方法。

回顾这六年,Go 团队并没有因为旧 API 存在缺陷,就急于推倒重来。

他们先识别问题,再重新设计 API;先构建原型,再邀请社区参与;先通过实验让真实项目检验,再最终承诺稳定性;同时,还要考虑如何让新旧实现长期共存。

这是一种相当克制的工程演进方式。

尤其是在今天,AI 让代码生成和重构的速度越来越快,写出一个新的 JSON 库或许并不困难。

但如何保证它能被数百万个 Go 项目安全使用十年甚至更久?

这才是真正困难的事情。

六年时间,Go 团队重写的不只是 JSON 库,更是为一个成熟生态重新设计基础设施的方式。

而这,或许才是 Go JSON v2 留给开发者最值得思考的东西。

参考资料:

  1. A new Go API for JSON - https://go.dev/blog/jsonv2
  2. A new experimental Go API for JSON - https://go.dev/blog/jsonv2-exp
  3. encoding/json/v2 Migration Guide - https://go.dev/doc/jsonv2-migration
  4. Go JSON v2 — Hacker News 讨论 - https://news.ycombinator.com/item?id=50369106
  5. Go 1.27 Release Notes - https://go.dev/doc/go1.27

还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。