Go解析JSON时基础类型字段遇null会panic,应优先用*string等指针类型区分“未提供”“null”“有值”三态;sql.NullString适用于需明确区分“缺失”与“显式null”的场景,如PATCH接口。

Go 解析 JSON 时 nil 指针字段直接 panic?用 sql.NullString 那套思路不行
Go 的 json.Unmarshal 对 *string 这类指针字段,遇到 JSON 中的 null 会设为 nil,不 panic;但如果你用的是普通 string 字段,又没加 omitempty,而 JSON 给了 null,就会报错:json: cannot unmarshal null into Go struct field XXX of type string。
根本原因:Go 的基础类型(string、int、bool)不能表示“不存在”或“空值”,而 JSON 的 null 明确表达了这个语义。所以必须用可空类型承载它。
- 别试图给
string字段加自定义UnmarshalJSON去“吞掉 null”——容易掩盖真实缺失字段问题 -
sql.NullString是为数据库设计的,带Valid字段,但 JSON 场景里多数时候你只关心“有没有值”,不需要额外布尔标记 - 更轻量的做法是统一用指针:
*string、*int64、*bool,它们天然能区分“没传”“传了 null”“传了有效值”
字段该用 *string 还是 sql.NullString?看你的业务语义是否需要区分“未提供”和“明确设为 null”
大多数 HTTP API 场景下,前端传 {"name": null} 和根本不传 "name" 字段,对后端业务逻辑是一回事:这个字段不参与更新或校验。这时候 *string 足够,且干净:
type User struct {
Name *string `json:"name"`
Age *int64 `json:"age"`
}
如果业务上必须区分——比如 PATCH 接口要支持“清空姓名”(显式 null)vs “不修改姓名”(字段缺失),那才需要 sql.NullString 或自定义类型:
立即学习“go语言免费学习笔记(深入)”;
type NullableString struct {
Valid bool
Str string
}
func (ns *NullableString) UnmarshalJSON(data []byte) error { ... }
-
*string:三态语义清晰(nil= 未提供 / null;非 nil = 有值),零成本,推荐默认选它 -
sql.NullString:多一个Valid字段,但Valid=false无法区分“字段缺失”和“字段为 null”,反而模糊语义 - 自定义类型如
NullableString:仅当协议强制要求四态(缺失 / null / 空字符串 / 非空字符串)才值得投入
json.RawMessage 不是万能解药,滥用会导致延迟解析失败难定位
有人看到 null 问题就切到 json.RawMessage,想着“先存着,后面再解析”。但它只是把字节存下来,不解决语义问题,还埋下隐患:
- 后续调用
json.Unmarshal解析RawMessage时,依然会遇到同样的 null 错误,只是推迟到了运行时更深层的地方 - 丢失结构体字段约束,IDE 无法提示,重构易出错
- 如果字段本该是
*int64,却用json.RawMessage存,等于放弃类型安全
真正适合 RawMessage 的场景只有两个:
– 字段结构完全动态(比如 webhook payload 中的 data)
– 你需要原样透传某段 JSON 不做解析(比如存配置快照)
嵌套结构体里的可选字段,别漏掉每一层的指针声明
常见错误:顶层用了 *string,但嵌套结构体内部还是用基础类型,结果 JSON 里嵌套对象为 null 时直接崩:
type User struct {
Profile *Profile `json:"profile"` // ✅ 外层指针
}
type Profile struct {
Bio string `json:"bio"` // ❌ 这里没指针!{"profile": null} 会 panic
}
- 只要 JSON 可能为
null,对应 Go 字段就必须是可空类型(指针 / 自定义可空类型) - 嵌套越深,越容易在某一层漏掉指针——建议写完结构体后,对着 JSON Schema 或实际样例逐字段检查 null 容忍性
- 用
go vet -tags=json或静态检查工具(如nilness)能发现部分未解引用的nil指针,但无法捕获反序列化阶段的类型不匹配
最麻烦的不是写错类型,而是团队里有人坚持“字段都该有默认值”,然后给所有字段配 "" 或 0 —— 这会让 null 语义彻底消失,后续加新字段、改接口、查数据异常时全靠猜。










