如何在Golang中处理JSON Null值_Web API数据序列化陷阱

5次阅读

gojson.unmarshal 默认将 json NULL 映射为零值而非 nil,导致无法区分字段缺失、为 null 或存在但全零;应使用 *user 或自定义 null 类型(如 nullString)并实现 unmarshaljson/marshaljson,避免语义混淆与运行时 panic。

如何在Golang中处理JSON Null值_Web API数据序列化陷阱

Go 的 json.Unmarshal 默认把 JSON null 映射成零值,不是 nil

这是最常被误读的一点:当 API 返回 {"user": null},而你用 type Resp Struct { User User } 去解码,User 字段不会是 nil,而是 User{}(即所有字段取零值)。这导致你无法区分“用户字段缺失”“用户字段为 null”“用户存在但字段全为零”三种情况。

正确做法是用指针sql.Null* 类型的变体(如 *User),让 Go 能表达“不存在”语义:

type Resp struct {     User *User `json:"user"` }

这样遇到 "user": null 时,Resp.User 就是 nil;遇到 "user": {} 才会初始化一个空 User 实例。

sql.NullString 风格自定义类型处理可空基础字段

对于字符串、数字、布尔这类基础类型,直接用 *string 虽然可行,但容易在业务逻辑里反复判空,也藏不住“本该非空却为 null”的语义。更稳妥的是仿照 sql.NullString 自定义类型:

立即学习go语言免费学习笔记(深入)”;

type NullString struct {     String string     Valid  bool }  func (ns *NullString) UnmarshalJSON(data []byte) error {     if string(data) == "null" {         ns.Valid = false         return nil     }     return json.Unmarshal(data, &ns.String) }
  • 它显式暴露 Valid 字段,避免业务层靠 != nil 猜意图
  • 注意:必须实现 UnmarshalJSON,否则嵌套结构中仍会静默转为零值
  • 别忘了同时实现 MarshalJSON,否则序列化回 JSON 时可能出错

第三方库 guregu/null 的坑:不兼容 omitempty 且默认不导出字段

很多人图省事用 guregu/null.String,但它有个隐蔽行为:其内部 String 字段是小写开头(string),导致 json.Marshal 默认忽略它,即使你写了 json:",omitempty" 也没用——因为字段不可导出。

实际效果是:guregu/null.String{Valid: false} 序列化出来是空对象 {},而不是预期的省略或 null

  • 要么改用它提供的 MarshalJSON() 方法手动控制
  • 要么换用 github.com/lib/pq.NullString(仅限 postgresql 场景)
  • 或者干脆自己写轻量版,字段名大写 + 显式实现编解码

API 响应中混合 null / missing / zero 的调试技巧

当上游返回混乱数据(比如有的字段是 null,有的压根没字段,有的传了 0""),光看日志很难定位。建议在开发期加一层“透明解码器”:

func DebugUnmarshal(data []byte, v interface{}) error {     fmt.Printf("Raw JSON: %sn", data)     return json.Unmarshal(data, v) }

再配合结构体字段加 tag 注释,例如:

type User struct {     ID    int64      `json:"id"`              // required, never null     Name  *string    `json:"name"`            // optional, may be null     Email NullString `json:"email,omitempty"` // nullable + omit when empty }

字段语义越明确,后续维护时越不容易把 nil"" 用,或者把 0 当“未设置”处理。

真正麻烦的从来不是怎么写 UnmarshalJSON,而是团队里有人悄悄把 *string 解引用成 string 再传给下游,结果 panic 发生在凌晨三点的告警里。

text=ZqhQzanResources