编程 encoding/json/v2 转正:默认拒绝重复 key 与非法 UTF-8,旧代码怎么过渡

2026-11-14 21:02:32

encoding/json/v2 转正:默认拒绝重复 key 与非法 UTF-8,旧代码怎么过渡

encoding/json/v2 随 Go 1.27 转正(2026-08-19)。新包提供 Marshal、MarshalWrite、MarshalEncode、Unmarshal、UnmarshalRead、UnmarshalDecode,全部接受可变 Options;配套的 encoding/json/jsontext 提供基于 Token/Value 状态机的 Encoder/Decoder。

v2 默认更严格

两处默认行为变化最容易在线上暴露:

  • JSON 字符串中的非法 UTF-8 直接拒绝,不再静默替换。
  • 同一对象内的重名 key 直接拒绝,不再后者覆盖前者。
package main

import (
	"fmt"

	"encoding/json/v2"
)

func main() {
	// 同一对象内重复 key:v2 默认拒绝
	var m map[string]int
	err := json.Unmarshal([]byte(`{"a":1,"a":2}`), &m)
	fmt.Println(m, err)

	// 字符串值里含非法 UTF-8 字节:v2 默认拒绝
	var s struct{ S string }
	err = json.Unmarshal([]byte("{\"S\":\"\xff\"}"), &s)
	fmt.Println(s, err)
}

这两条不需要通过 Options 打开,是默认语义。对上游数据不可控的服务,重复 key 与坏编码从「悄悄放过」变成「显式报错」,需要确认调用方能否接受。

v1 由 v2 支撑,错误文本可能变

encoding/json(v1)现在由 v2 实现支撑:编解码行为保留,但错误信息文本可能不同。用错误字符串做分支判断或做断言比对的代码要重新核对。v1 API 继续支持,不要求迁移。

v1 包新增了若干 Options,可以在调用点让 v2 按 v1 语义运行。这是渐进迁移的主要手段:先定位差异点,再在具体调用点固定旧语义,而不是整体改写。性能上 Marshal 大致相当,Unmarshal 明显更快。

临时回退

构建时设置 GOEXPERIMENT=nojsonv2 可恢复旧的 v1 实现。该开关未来会移除,只适合作为短期兜底,不适合长期依赖。

GOEXPERIMENT 期间被移除与变更的项

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

inline 到 embed 是纯改名,全局替换即可:

// 旧
type User struct {
	Base
	Name string `json:"name"`
	Meta string `json:",inline"`
}

// 新
type User struct {
	Base
	Name string `json:"name"`
	Meta string `json:",embed"`
}

string tag 的行为更新(#79065)要按字段回归:凡是标了 json:",string" 的字段,编码结果在边界情况下可能与之前不同。MatchCaseInsensitiveNames 的行为更新(CL 792780)则影响依赖大小写不敏感字段名匹配的代码路径,按名字匹配字段的逻辑需要单独验证。

jsontext 的数值访问器

jsontext 的数值 Token 访问器现在同时返回 error(#77666)。原来直接取值的调用点要改成两值接收,并处理解析失败:

// 示意:数值访问器现在返回 (值, error)
if tok.Kind() == jsontext.KindNumber {
	n, err := tok.Int64()
	if err != nil {
		// 超出范围或不是合法整数表示
	}
	_ = n
}

迁移检查清单

  1. 先不传 Options 跑全量测试,记录被拒的输入与依赖错误文本的断言。
  2. 针对具体调用点用 v1 新增的 Options 固定旧语义,逐点收敛,不做全局一刀切。
  3. 全局搜 json:",inline",改为 json:",embed"。
  4. 对 json:",string" 字段做编码回归,比对输出差异。
  5. 检查 MatchCaseInsensitiveNames 相关路径的匹配结果。
  6. 替换已移除的 format tag、unknown tag、DiscardUnknownMembers marshal 选项、SkipFunc 哨兵错误。
  7. 检查 jsontext 里所有数值 Token 取值处,补上 error 分支。
  8. 确认线上是否存在重复 key 或非法 UTF-8 的历史数据;必要时在入口层清洗,而不是靠 v2 放行。
  9. 回退方案写明 GOEXPERIMENT=nojsonv2,并标注该开关会被移除的时限约束。
复制全文 生成海报 Go JSON 迁移

推荐文章

程序员茄子在线接单