json.Unmarshal返回nil表示解析成功,数据已写入传入的指针变量;常见原因包括传值而非地址、结构体字段未导出(小写)、缺失json tag或类型不匹配。

为什么 json.Unmarshal 返回 nil 却没拿到数据
因为 json.Unmarshal 从不“返回”解析结果,它只负责把数据写进你传进去的变量里。返回值 error 是纯信号:等于 nil 表示写入成功,非 nil 表示失败。
常见静默失败场景:
- 传了
p而不是&p→ 编译通过,但结构体字段全为零值(Go 值传递) - 打印
err == nil就结束,没看p本身 → 其实已经填好了,只是你没检查 - 结构体字段是小写开头(如
name string)→ 完全忽略,不报错也不赋值
调试时加一句:fmt.Printf("→ %+v", p),比只盯 err 有用十倍。
结构体字段必须大写 + 正确用 json tag
Go 的 json 包只处理导出字段(首字母大写),且默认按字段名匹配 JSON key —— 但外部 API 几乎不用驼峰命名。所以光大写不够,还得靠 json:"xxx" 标签对齐。
立即学习“go语言免费学习笔记(深入)”;
关键点:
-
User_id int `json:"user_id"`→ JSON 中是"user_id": 123,才能正确写入 -
CreatedAt time.Time `json:"created_at"`→ 字段名和 tag 分离,语义清晰 -
Options *[]string `json:"options,omitempty"`→omitempty只影响序列化,反序列化时该字段缺失就保持原值 - 嵌套结构体同理:内层字段也得大写 + tag,漏一层就整个子结构为零值
遇到 null、类型不匹配、缺失字段会怎样
json.Unmarshal 的容错策略很务实,但也容易埋坑:
- JSON 是
null,目标字段是*string→ 赋值为nil(合理) - JSON 是
null,目标字段是string→ 不修改原值(若未初始化就是空字符串) - JSON 字段缺失(如没有
"email")→ 对应字段保持原值,不会清空 - JSON 字符串塞进
int字段 → 直接返回json.UnmarshalTypeError,不是跳过
这意味着:想让某个字段“必须存在”,不能依赖 Unmarshal 自动校验,得自己写逻辑,比如 if p.Email == "" { return errors.New("email required") }。
别反复 Unmarshal 同一段 []byte
每次调用 json.Unmarshal(data, &v) 都会重新做词法分析和语法树构建,开销不小。尤其在 API 网关或高频解析场景下,重复解析同一段原始 JSON 很浪费。
更高效的做法:
- 先用
json.RawMessage接住不确定结构的子字段,延迟解析 - 或一次性解析成
map[string]interface{},再按需取值并手动转类型 - 注意:
json.RawMessage保存的是原始字节引用,如果原始[]byte被复用或释放,它会失效
真正难的不是写对第一行 json.Unmarshal,而是后续怎么安全地验证字段存在性、怎么处理时间格式、怎么避免零值被误用——这些都得靠显式判断,而不是指望 Unmarshal 替你兜底。


















