go json.unmarshal 遇到 null 时不会自动将指针字段置为 nil,除非该指针初始为 nil;推荐用 json.rawmessage 手动判断 null 或实现自定义 unmarshaljson 方法精准控制语义。

Go json.Unmarshal 遇到 null 时指针字段不置 nil
默认情况下,json.Unmarshal 不会把 JSON 中的 null 自动赋给结构体里的指针字段为 nil——它只在字段是“零值可设”且类型匹配时才覆盖;而指针字段若已初始化(比如指向一个零值),null 就被静默忽略。
常见错误现象:{"name": null} 解析进 struct{ Name *string } 后,Name 仍是旧指针(可能指向空字符串或随机地址),不是 nil。
- 必须确保字段是未初始化的指针(即
nil)才能被null正确覆盖 - 如果结构体是复用的(如从 sync.Pool 获取),记得手动清零指针字段
- 别依赖
json.RawMessage绕过——它只是延迟解析,不解决语义问题
用 json.RawMessage + 手动解包判断 null
这是最可控的方式:先把字段当原始字节存着,后续按需解析,并显式检查是否为 null。
使用场景:API 响应中某些字段可能为 null,但业务逻辑需要严格区分“未提供”、“明确为空”、“有值”三种状态。
立即学习“go语言免费学习笔记(深入)”;
type User struct {
Name json.RawMessage `json:"name"`
}
func (u *User) GetName() (*string, error) {
if len(u.Name) == 0 {
return nil, nil // 字段缺失
}
if string(u.Name) == "null" {
return nil, nil // 显式 null
}
var s string
if err := json.Unmarshal(u.Name, &s); err != nil {
return nil, err
}
return &s, nil
}
-
json.RawMessage不触发自动解码,避免中间态污染 - 注意
string(u.Name) == "null"是安全的——json.RawMessage保证格式合法 - 别用
bytes.Equal(u.Name, []byte("null")),因为可能带空格或换行(实际不会,但语义上更松散)
自定义 UnmarshalJSON 方法处理 null 语义
给结构体或字段类型实现 UnmarshalJSON,就能完全接管 null 的行为。比 RawMessage 更干净,适合高频复用的字段类型。
参数差异:方法接收者必须是指针,否则无法修改原值;返回 error 时整个解码失败。
type NullableString struct {
Valid bool
Value string
}
func (n *NullableString) UnmarshalJSON(data []byte) error {
if len(data) == 0 || string(data) == "null" {
n.Valid = false
return nil
}
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
n.Value = s
n.Valid = true
return nil
}
- 别漏掉
n.Valid = false——否则旧值残留,导致误判 - 性能影响很小,但要注意嵌套层级深时,每个自定义类型都多一次函数调用
- Go 1.20+ 可配合泛型封装通用
Nullable[T],但需权衡可读性
第三方库如 gjson 或 mapstructure 的取舍
gjson 是只读解析器,不走结构体绑定,天然规避指针赋值问题;mapstructure 默认把 null 转成目标类型的零值(对指针就是 nil),但需显式配置 WeaklyTypedInput: true。
兼容性影响:两者都不依赖 Go 标准库的 json 标签逻辑,意味着结构体字段名、嵌套规则要单独维护。
-
gjson适合只取几个字段、且 JSON schema 不稳定的情况 -
mapstructure的null行为不是默认开启的,必须传DecodeHook或启用弱类型模式 - 引入新依赖前,先确认你是否真的需要绕过标准
json包——多数时候定制UnmarshalJSON更轻量
最容易被忽略的是:无论用哪种方式,只要涉及指针字段,都要检查上游数据是否真包含 null 字面量,而不是空字符串、空对象或缺失字段——这三者语义完全不同,但错误日志里常常混作一团。










