json.Unmarshal 会 panic 而非返回 error,是因为源码中显式在 unmarshal 函数末尾 panic(d.savedError);常见于 nil 指针解码、未导出字段、自定义 UnmarshalJSON 中 panic 等场景。

json.Unmarshal 为什么有时 panic 而不是返回 error?
因为 json.Unmarshal 在遇到无法修复的类型不匹配(比如向 *int 写入 null,且该指针为 nil)时,会直接 panic,而不是走 error 分支。这是源码里显式写的逻辑,在 decode.go 的 unmarshal 函数末尾有 if d.savedError != nil { panic(...) }。
常见触发场景:
- 传入 nil 指针:如
var v *string; json.Unmarshal(b, v) - 结构体字段是未导出(小写开头),但 JSON key 存在且非空
- 自定义
UnmarshalJSON方法里调用了panic或引发 runtime error
实操建议:永远用 if err := json.Unmarshal(b, &v); err != nil 包裹,别依赖 recover;若需容错,提前检查字节切片是否为空或含非法字符,或用 json.Valid 预检。
struct tag 中的 “-” 和 “omitempty” 在源码里怎么处理?
两者都在 type.go 的 cachedTypeFields 函数中解析。结构体反射后,每个字段的 tag 被拆成 key-value 对,- 会让字段被完全跳过(name == ""),而 omitempty 只影响 marshal 行为(在 encode.go 的 structEncoder.encode 中判断值是否“零值”)。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
注意点:
-
omitempty对指针、map、slice、interface{} 判空是看是否为 nil;对数值类型(int,float64)判空是看是否为 0;对 string 是看是否为"" - 嵌套结构体字段带
omitempty,但外层字段非零,仍会编码该嵌套结构体(除非它本身是零值) - tag 里多个空格或换行会被
strings.TrimSpace清掉,但逗号不能有空格:`json:"name,omitempty"` ✅,`json:"name, omitempty"` ❌(会当成两个 option)</li> </ul> <H3>为什么 []byte 比 string 更快地参与 json 解析?</H3> <p>因为 <code>json.Unmarshal
接收的是[]byte,源码中所有解析路径都基于字节切片做游标移动(d.read(),d.peek()),避免了 string → []byte 的重复转换。如果你传的是string,Go 会隐式转成[]byte(底层数组拷贝),而[]byte可直接复用内存。性能关键点:
- 从 HTTP body 读取时,优先用
io.ReadFull+ 预分配[]byte,别用body.ReadAsText()或string(body) - 如果数据来自 mmap 或需要零拷贝,可借助
unsafe.Slice(Go 1.17+)把 string header 转成[]byte,但必须确保 string 数据不会被 GC 回收(比如它是常量或生命周期明确) - 源码中
decodeState的data字段是[]byte,所有 skip/parse 操作都靠off偏移和len(data)边界控制,没任何 string 转换开销
自定义 UnmarshalJSON 时,如何避免无限递归?
典型错误是:在
T.UnmarshalJSON里又调用json.Unmarshal解析整个原始字节,而该调用又回到T.UnmarshalJSON,形成循环。源码中unmarshal函数检测到当前类型正在解码中(通过d.depth和typeCache的递归标记)时,会报错"recursive unmarshal"。正确做法:
- 用
json.RawMessage暂存原始字节,再在方法内做二次解析(此时深度已降) - 手动解析 token 流:用
d.nextToken()+d.literal()逐个读,避开顶层Unmarshal - 若需复用标准逻辑,可新建一个
json.Decoder并设UseNumber()等选项,但输入必须是io.Reader,得包装成bytes.NewReader
最容易被忽略的是:即使你没显式调用
Unmarshal,只要用了json.NewDecoder(r).Decode(&v)且v类型是当前正在解码的类型,同样会触发递归检测——因为Decoder.Decode底层还是走同一套decodeState逻辑。 - 从 HTTP body 读取时,优先用

















