最稳路径是直接用 json.unmarshal 解析到结构体:需字段首字母大写、严格镜像 json 层级、用 json:"key" 标签映射,嵌套对象须定义独立 struct,动态 key 用 map[string]t,混型数组用 json.rawmessage 延迟解析。

直接用 json.Unmarshal 解析到结构体,别先转 map[string]interface{}——这是最稳、最易维护、IDE 可补全、编译期能报错的路径。硬解嵌套 JSON 时掉进 map[string]interface{} 坑里,90% 的 panic 和后期改不动都源于此。
结构体必须严格镜像 JSON 层级
Go 不会自动“展开”或“跳层”,json.Unmarshal 只认你写的字段名 + json: 标签是否与当前层级 key 完全对应。写错一层,整块数据就为空或 panic。
- 嵌套对象(如
{"user": {"name": "A"}})必须定义独立type User struct,再在顶层 struct 中声明字段User User `json:"user"` - 别用匿名字段:写
User struct { Name string }是错的,它会被当成嵌入,期望 JSON 是{"name":"A"},而不是{"user":{"name":"A"}} - 所有字段首字母必须大写,否则
encoding/json完全不可见,值永远是零值 - JSON 键是小写下划线(如
created_at),字段名得配CreatedAt int64 `json:"created_at"`
动态 key(如 ID 当键)只能用 map[string]T
看到 {"1001": {"name": "A"}, "2002": {"name": "B"}} 这种结构,第一反应不是切片,而是 map[string]User。用 []struct{} 或 []User 会直接报错:json: cannot unmarshal object into Go value of type []User。
用于端到端视频本地化流程的轻量编排器,路由至四个专注子技能——/wjs-transcribing-audio、/wjs-translating-subtitles...
- 定义
type UserMap map[string]User,然后json.Unmarshal(data, &userMap) -
T必须是具名类型(如User),别用map[string]interface{},否则下一层取值又要断言,重复踩坑 - key 是数字?JSON 标准只允许字符串 key,服务端返回的数字实际已转成字符串,Go 侧按
string处理即可
数组里混着不同结构?用 json.RawMessage 延迟解析
当 JSON 数组元素类型不一致(如 [{"type":"user","name":"A"},{"type":"admin","level":9}]),直接解到统一 struct 必然失败。此时不能靠运行时判断类型再重解,而应让 RawMessage 把原始字节缓存下来。
- 字段声明为
Items []json.RawMessage `json:"items"`,json.RawMessage是[]byte别名,不触发解析,零拷贝 - 遍历时对每个
item先json.Unmarshal(item, &t)到临时 struct 提取Type字段,再二次解码到对应目标 struct - 不能把
json.RawMessage当局部变量直接json.Unmarshal,它必须作为 struct 字段存在
null / 缺失 / 类型不固定的字段要慎设类型
像 "RT": null 或 "gtAns": true 或 "gtAns": "yes" 这类字段,硬设 string 或 bool 会导致解码失败。Go 没有联合类型,但有更稳的绕法。
- 用
*json.RawMessage:指针 + RawMessage 可安全接收null、缺失、任意 JSON 值,后续再按需解析 - 用
interface{}:仅限真正动态的字段,别用它接整个结构体,那只是把问题往后推 - 字段可能缺失?用指针类型(如
*string、*int)或sql.NullString,避免解包失败
最常被忽略的是:空嵌套对象 {} 解到 struct 得到全零值,解到 map[string]interface{} 得到空 map,二者行为不同;还有 nil slice 和空 slice 在序列化时表现也不同——这些边界情况不提前想清楚,上线后排查成本极高。










