编程 升级 Go 1.27 后 JSON 接口的行为变化:v1 默默接受的输入,v2 默认报错

2026-10-22 21:02:02

升级 Go 1.27 后 JSON 接口的行为变化:v1 默默接受的输入,v2 默认报错

参考:Go 1.27 Release Notes

Go 1.27 引入了 encoding/json/v2 和 encoding/json/jsontext 两个包,同时把现有的 encoding/json 换成了 v2 实现作为后端。多数代码升级后表现不变,但会接受非法输入的那部分线上行为,可能在这类升级中暴露出来。

v1 容错、v2 报错的两类输入

v2 的默认值比 v1 更严格、更强调互操作性,其中两条最容易在接口联调里撞上:

  • JSON 字符串里的非法 UTF-8。v1 会把非法字节静默替换成 U+FFFD,然后照常返回字符串;v2 直接拒绝。
  • 同一个 JSON 对象内的重复键。v1 是后者覆盖前者,写得进去也读得出来;v2 视为错误。

这两类数据在真实系统里通常来自拼接字符串、老旧服务端、日志回放或第三方回调。v1 时期它们不会报错,所以很难在测试里被发现,往往要等到流量上来才出现。

encoding/json 已经跑在 v2 上

encoding/json 现在由 v2 实现支撑。序列化和反序列化的行为是保留的,但错误信息的具体文案可能会变。如果代码或告警系统依赖 err.Error() 的字符串做匹配、或者依赖错误文本里的位置描述,升级时要一并检查。

v1 API 会继续支持,不强制迁移到 v2 新接口。Marshal 性能与之前大致持平,Unmarshal 明显更快,但具体数字以自己压测为准。

定位方法

1. 看报错原文形态。 v2 的报错一般会带出错位置和具体原因,先在日志里把原始错误完整打出来,不要只留下 json: cannot unmarshal ... 这类被截断的前缀,再对照输入报文找出是哪个字符串或哪个重复键。

2. 用 encoding/json/jsontext 做流式校验。 jsontext 提供的是低层语法处理,Encoder / Decoder 以 Token 和 Value 序列操作 JSON,内部维护状态机保证产出或消费的序列是合法 JSON 文本。适合在网关或中间件里对可疑报文单独跑一遍语法校验,把问题数据先筛出来,而不是等它进到业务结构体才发现。

3. 按需开启 v1 语义的 Options。 encoding/json 新增了一批 Options,可以配置 v2 以 v1 的语义运行,不必为此整体迁移到新 API。适合改造面太大、需要分批推进的场景:先让接口恢复原有行为,再逐个消费方收紧。

4. GOEXPERIMENT=nojsonv2 兜底。 构建时设置这个环境变量会恢复到原来的 v1 实现,用于紧急回滚。这是临时手段,官方说明该 opt-out 预计会在未来版本移除。遇到兼容性问题也应提 issue。

v2 移除和改名的选项

在 GOEXPERIMENT 阶段,encoding/json/v2 有几处值得注意的变化:

  • 移除 format tag 选项(#79071)
  • 移除 unknown tag 选项(#77271)
  • 移除 DiscardUnknownMembers marshal 选项(#77271)
  • 移除 SkipFunc 哨兵错误(#74324)
  • inline tag 选项改名为 embed(#79985)
  • string tag 选项的行为有更新(#79065)
  • MatchCaseInsensitiveNames 选项行为有更新(CL 792780)
  • 在 encoding/json/jsontext 中,数值型 Token 的访问器改为同时返回 error(#77666)

前四项是直接消失,代码里如果还写着这些 tag 或选项,编译期或运行期都会出问题;inline → embed 是纯改名,需要同步改结构体 tag。string 和大小写不敏感匹配属于行为调整,同一份配置在新版本下可能得到不同结果,升级后建议针对这两处补回归用例。

背景和更多细节见提案 issue;如需关闭新实现,请提 issue 说明。

复制全文 生成海报 Go encoding json JSON 升级兼容

推荐文章

程序员茄子在线接单