最有效做法是用json.rawmessage延迟解析、json.token手动跳过无关字段、结合json.decoder流式解码;避免interface{}和重复unmarshal,通过结构体字段建模与状态机控制解析边界。

Go 标准库的 json.Decoder 本身已支持流式解码,无需“自定义”底层解析器;真正需要定制的是**如何在不解完整体 JSON 的前提下,按需提取嵌套结构、跳过无关字段、或处理不规则/混合类型字段**——这靠组合 json.RawMessage、json.Token 和手动状态机实现,不是重写解析器。
用 json.RawMessage 延迟解析嵌套对象或数组
当你只关心顶层字段,但某个字段(如 "data")结构动态或体积大,直接解到 struct 会触发完整反序列化并占用内存。此时把该字段声明为 json.RawMessage,后续再按需解码:
type Event struct {
ID int64 `json:"id"`
Type string `json:"type"`
Data json.RawMessage `json:"data"` // 不立刻解析
}
<p>var evt Event
if err := json.NewDecoder(r).Decode(&evt); err != nil {
// handle error
}
// 后续根据 evt.Type 决定如何解 evt.Data
switch evt.Type {
case "user_created":
var u UserCreated
if err := json.Unmarshal(evt.Data, &u); err != nil { /<em> ... </em>/ }
case "order_updated":
var o OrderUpdated
if err := json.Unmarshal(evt.Data, &o); err != nil { /<em> ... </em>/ }
}</p>
注意:多次调用 json.Unmarshal 会对同一 json.RawMessage 多次解析,无性能问题,但别误以为它是“引用”——它只是字节切片,内容不会变。
用 json.Decoder.Token() 手动遍历跳过未知字段
标准 Decode() 遇到 struct 中未定义的字段会报错(除非加 json:"-" 或启用 DisallowUnknownFields())。若输入 JSON 字段不可控(如第三方 API),又不想定义超大 struct,可用 token 级 API 跳过:
- 调用
dec.Token()获取下一个 token(json.Delim、string、float64等) - 遇到未知 key 时,用
dec.Skip()跳过其整个值(包括嵌套对象/数组) - 只在识别到目标 key(如
"id")时读取对应值
典型场景:从海量日志行中提取 "status" 和 "duration_ms",其余字段全忽略。比定义 struct + 忽略字段更省内存,尤其当 JSON 很宽(上百字段)但只取两三个时。
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
处理混合类型字段(如 "value": 123 或 "value": "abc")
JSON 规范允许同一字段出现不同类型,Go 的 struct 解码会失败。安全做法是先用 json.RawMessage 拿到原始 bytes,再尝试多种解码路径:
type Metric struct {
Name string `json:"name"`
Value json.RawMessage `json:"value"`
}
<p>func (m *Metric) GetInt() (int64, error) {
var i int64
return i, json.Unmarshal(m.Value, &i)
}</p><p>func (m *Metric) GetString() (string, error) {
var s string
return s, json.Unmarshal(m.Value, &s)
}</p>
不要用 interface{} + 类型断言——它仍会触发完整解析且无法区分 number 和 string 在 unmarshal 后的 float64 表示;json.RawMessage 是唯一能保留原始类型语义的方案。
流式解码中的常见陷阱
真正容易出错的不是“怎么写”,而是对 Go JSON 解码模型的误解:
-
json.Decoder是流式的,但json.Unmarshal总是加载全部 bytes —— 别在循环里对大json.RawMessage反复调用Unmarshal还以为“流式” - 用
Token()时,如果跳过一个对象但没配对调用Skip()(比如只读 key 没读 value),解码器状态会错乱,后续所有读取都失败 -
json.RawMessage持有的是源数据的 slice 引用,如果原始 reader(如bufio.Reader)被复用或 buffer 被覆盖,它可能指向无效内存 —— 生产环境务必拷贝:copy(dst, src)
流式解码的核心约束始终是:你得自己管理解析边界和状态。标准库没帮你做“智能跳过”,它只提供原子操作;所谓“自定义”,本质是你用这些原子操作搭出符合业务边界的解析逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










