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=1 | Go 1.28+ | 正常,新旧两种编码都能解 |
netmarshal=1 | Go < 1.28 | base64 解析失败 |
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 被移除。长期看最终只剩可读形式,只是现在还没到那一步。