编程 Go 1.28 加了 netmarshal,默认还是 0:JSON 里的 IPMask 与 IPNet 仍是一串 base64

2026-11-06 21:01:54

Go 1.28 加了 netmarshal,默认还是 0:JSON 里的 IPMask 与 IPNet 仍是一串 base64

netmarshal 是 Go 1.28 新增的 GODEBUG 设置,只控制一件事:net.IPMask、net.IPNet、net.HardwareAddr 这三个类型做文本序列化时,写成可读的地址文本,还是写成一串 base64。

  • netmarshal=0:不可读,走 base64。
  • netmarshal=1:可读,写成 IP、CIDR、MAC 这类文本形式。

Go 1.28 和 1.29 的默认值都是 0。这样,这两个版本生成的 JSON 仍能被旧版 Go 读取。按计划,Go 1.30 才会把默认值改为 1。读取侧不受影响:Go 1.28 及之后的所有版本,无论数据是旧编码还是新编码,都能解析。

两种编码的对照

type Iface struct {
	Net  net.IPNet
	Mask net.IPMask
	MAC  net.HardwareAddr
}

func main() {
	_, ipnet, _ := net.ParseCIDR("192.0.2.0/24")
	mac, _ := net.ParseMAC("00:11:22:33:44:55")
	v := Iface{Net: *ipnet, Mask: ipnet.Mask, MAC: mac}

	b, _ := json.Marshal(v)
	fmt.Println(string(b))
}

netmarshal=0(1.28/1.29 的默认值),掩码和 MAC 都会编码成 base64:

{"Net":{"IP":"wAACAA==","Mask":"////AA=="},"Mask":"////AA==","MAC":"ABEiM0RV"}

netmarshal=1,各类型都取其文本形式:IPNet 写成 CIDR,HardwareAddr 写成 MAC 串:

{"Net":"192.0.2.0/24","Mask":"255.255.255.0","MAC":"00:11:22:33:44:55"}

字段名和类型都没变,仍然是 JSON 字符串,变的只是字符串里的内容。

默认值怎么算、怎么查

GODEBUG 的最终值按以下顺序确定:先取编译所用工具链的默认值,再按 go.mod 里的 go 版本修正,最后被源文件顶部的 //go:debug 覆盖。

查看某个主包最终编进去的默认值:

go list -f '{{.DefaultGODEBUG}}' ./cmd/server

按模块固定

从 Go 1.23 起,go.mod / go.work 里可以写 godebug 块:

module example.com/svc

go 1.28

godebug (
	netmarshal=1
)

也可以把设置放在单个文件里:

//go:debug netmarshal=1
package main

有两条边界:只有工作模块的 go.mod 会被读取,依赖库在自己 go.mod 里写 godebug 不生效;测试文件里的 //go:debug 会作为测试主包的指令生效。因此这个开关是进程级的,写入哪个值由主包决定,不能指望某个库把它带进来。

兼容矩阵

写出方读取方结果
netmarshal=0任意版本正常
netmarshal=1Go 1.28+正常,新旧两种编码都能解
netmarshal=1Go < 1.28base64 解析失败
netmarshal=1非 Go 消费者(按 base64 写死了解码)失败

灰度期会怎么失败

服务 A 升级到 1.28,并在自己的 go.mod 里写 netmarshal=1,把序列化结果写进 Kafka 或数据库。消费者 B 还编译在 1.27 上,反序列化 net.IPMask 时直接解析失败。此时回滚 A 也没用:已经落盘的 JSON 里是可读文本,B 照样读不了,要么把 B 一起升上去,要么在中间加一层转换。

第二个容易忽略的点:你以为新老实例并行时能互相读。1.28+ 的读取侧确实两种都兼容,但早于 1.28 的实例不在这个范围内。灰度期间真正要确认的是最旧的读取方版本,而不是本地写出什么。

第三个坑在对外接口。字段名没变,还是字符串,但内容从 base64 变成了 CIDR / MAC 文本。下游不一定是 Go,可能是一个写死解码逻辑的网关或 Python 服务。

值得打开的场景

  • 整条链路中的读取方都已是 1.28+,并且没有历史落盘数据需要回头读:可以直接打开,日志和调试输出会好读很多。
  • 内部工具、CLI 输出、配置 dump:值得开启。
  • 对外 API、共享 topic、跨团队接口约定:先不要打开,等 1.30 默认翻转后统一处理;或者干脆把字段定义成自己的字符串类型,不让编解码格式由 GODEBUG 决定。

这个设置本身是过渡性的,最早可能在 Go 1.34 被移除。长期看最终只剩可读形式,只是现在还没到那一步。

复制全文 生成海报 Go GODEBUG 序列化 JSON

推荐文章

程序员茄子在线接单