编程 Go 1.27 的 encoding/json/v2:重复键和非法 UTF-8 从静默通过变成报错

2026-11-09 21:02:04

Go 1.27 的 encoding/json/v2:重复键和非法 UTF-8 从静默通过变成报错

Go 1.27 新增两个包:encoding/json/v2 与 encoding/json/jsontext。

encoding/json/v2 是 encoding/json 的大幅修订版,提供 Marshal、MarshalWrite、MarshalEncode、Unmarshal、UnmarshalRead、UnmarshalDecode,这些函数都接受可变参数 Options,用来配置具体行为。

encoding/json/jsontext 负责更底层的 JSON 语法处理:Encoder 与 Decoder 以 Token 和 Value 序列操作 JSON,并维护状态机,确保产出或消费的序列是合法 JSON 文本。

v2 默认值更严格

v2 的默认值比 v1 更严格,互操作性也更好,最直接的两条差异是:

  • 拒绝 JSON 字符串中的非法 UTF-8;
  • 拒绝同一 JSON 对象内的重复名字。

这两类输入在 v1 下都属于「能读就读」:字符串里的坏字节会被容错处理,重复键则由后出现的值覆盖先出现的值。v2 不再这样处理,直接报错。完整差异与可调整的选项见 v1 encoding/json 包文档。

v1 的行为保留,错误文案变了

encoding/json 现在由 v2 实现支撑。marshal / unmarshal 的行为保持不变,但错误消息的精确文本可能不同。这意味着不要用字符串比较来判断错误:判类型用 errors.As / errors.Is,不要把 err.Error() 的字面量写进分支逻辑,否则升级后会出现「逻辑没改、判断全失效」的情况。

用 Options 退回 v1 语义

encoding/json 新增了一些 Options,可以让 v2 以 v1 的语义运行,从而避免强制全量迁移。Unmarshal 支持可变参数 Options,需要旧行为的地方显式传参放宽,而不是整体切回旧实现——显式放宽的地方后续可以逐条审计。

性能

marshal 大致与旧实现持平,unmarshal 显著更快。

最适合出问题的地方:重复键

以前重复键取最后一个,配置合并、接口对接这种「多层叠加 + 后者覆盖前者」的写法天然依赖这个行为。切到严格默认后,这类输入会直接解析失败:

  • 配置合并:多份配置文本叠加、同一项被写了两遍;
  • 接口兼容旧字段:对端同时下发新旧字段名,或在同一对象里重复填充。

非法 UTF-8 同理,历史数据里混进的坏字节以前被无声吞掉,现在会在解析阶段暴露出来。

对比代码

package main

import (
	"fmt"

	jsonv1 "encoding/json"
	jsonv2 "encoding/json/v2"
)

func main() {
	// 同一对象内的重复名字
	dup := `{"name":"a","name":"b"}`

	var a, b map[string]string
	fmt.Println("v1:", jsonv1.Unmarshal([]byte(dup), &a), a)
	// v2 不传 Options 即走默认的严格模式
	fmt.Println("v2:", jsonv2.Unmarshal([]byte(dup), &b), b)

	// JSON 字符串中的非法 UTF-8
	bad := "{\"name\":\"\xff\"}"

	var c, d map[string]string
	fmt.Println("v1:", jsonv1.Unmarshal([]byte(bad), &c), c)
	fmt.Println("v2:", jsonv2.Unmarshal([]byte(bad), &d), d)
}

迁移建议

  1. 先不动 API:不换包、不改结构体标签,只升级 Go 版本跑一遍全量测试。
  2. 让失败点自己暴露:把重复键、非法 UTF-8 这两类样例补进解析入口附近的测试,或在联调环境里直接灌历史数据。
  3. 逐项决定:对每个失败点判断「这个输入本来就该被拒绝」还是「历史兼容更重要」。该拒绝的保留严格默认;需要兼容的,在该调用点显式传 v1 语义的 Options,不要为了省事全局放宽。

局限

v1 API 不会被废弃,继续支持,因此没必要为了「跟上版本」做全量迁移。引入 v2 的收益集中在三处:更严格的默认值、Options 带来的可配置行为,以及 jsontext 那层基于 Token / Value 的流式处理与状态校验。用不到这三点,留在 v1 是合理选择。

参考:

复制全文 生成海报 Go JSON 版本升级 错误处理

推荐文章

程序员茄子在线接单