
本文介绍如何在 go 中高性能地流式解析嵌套结构的 json 数组(如 `[{"a":1},{"b":2}]`),避免内存全量加载与重复扫描,推荐使用轻量级 json 扫描器(如 `megajson/scanner`)实现逐对象解码。
在 Go 标准库中,json.Decoder 天然支持流式解析——但仅限于多个独立 JSON 值连续排列(即 newline-delimited JSON,NDJSON),例如:
{"Name":"Ed","Text":"Knock knock."}
{"Name":"Sam","Text":"Who's there?"}而实际业务中,我们常遇到的是单个 JSON 数组([...])作为整体输入流,例如:
[
{"Name": "Ed", "Text": "Knock knock."},
{"Name": "Sam", "Text": "Who's there?"},
{"Name": "Ed", "Text": "Go fmt."}
]此时若调用 json.NewDecoder(r).Decode(&v),它会尝试一次性解码整个数组到切片,失去“流式”优势;若强行拆解字符串(如剥去 [] 后按 {...} 切分),又面临括号嵌套、引号转义、性能退化等难题。
✅ 推荐方案:基于词法扫描器(Scanner)的增量解析
不依赖完整语法树,而是逐字符识别 JSON 令牌(token),在状态机驱动下实时构建对象。megajson/scanner 是一个轻量、无反射、零分配(核心路径)的纯扫描实现,特别适合高吞吐、低延迟场景。
以下是一个完整可运行示例,解析上述 JSON 数组并构造 []Object:
package main
import (
"fmt"
"strings"
"github.com/benbjohnson/megajson/scanner"
)
type Object struct {
Name string `json:"Name"`
Text string `json:"Text"`
}
func main() {
rdr := strings.NewReader(`[
{"Name": "Ed", "Text": "Knock knock."},
{"Name": "Sam", "Text": "Who's there?"},
{"Name": "Ed", "Text": "Go fmt."}
]`)
s := scanner.NewScanner(rdr)
var objects []Object
// 状态机:跟踪是否处于键名/值上下文、当前对象字段
state := struct {
inObject bool // 是否已进入 { ... }
inKey bool // 当前 STRING token 是否为 key
lastKey string
obj Object
}{}
for {
tok, data, err := s.Scan()
if err != nil {
break // EOF or syntax error
}
switch tok {
case scanner.TLBRACE:
// '{' → 开始新对象
state.inObject = true
state.inKey = true
state.lastKey = ""
state.obj = Object{}
case scanner.TRBRACE:
// '}' → 对象结束,追加到结果
if state.inObject {
objects = append(objects, state.obj)
state.inObject = false
}
case scanner.TSTRING:
// 字符串 token:交替作为 key 或 value
str := string(data)
if state.inObject {
if state.inKey {
state.lastKey = str
} else {
// 根据 lastKey 赋值字段(支持任意嵌套需扩展为 map[string]interface{})
switch state.lastKey {
case "Name":
state.obj.Name = str
case "Text":
state.obj.Text = str
}
}
state.inKey = !state.inKey
}
// 忽略 TCOMMA、TLBRACKET、TRBRACKET、TNUMBER 等无关 token(本例无嵌套数值/布尔)
}
}
fmt.Printf("Parsed %d objects: %+v\n", len(objects), objects)
}? 关键设计说明:
- 状态驱动:仅用几个布尔/字符串字段维护解析位置,无递归、无栈分配;
- 零拷贝字符串提取:data 是 []byte 切片,直接 string(data) 转换(注意:若需长期持有,应显式拷贝);
- 可扩展性:若需支持嵌套对象或动态字段,可将 state.obj 替换为 map[string]interface{},并在 TLBRACE/TRBRACE 中管理嵌套层级栈;
- 错误处理:s.Scan() 在非法 JSON 时返回具体错误(如 scanner.ErrInvalidToken),便于日志与熔断。
⚠️ 注意事项:
- megajson/scanner 不验证 JSON 语义完整性(如 key 是否带引号),需确保输入合法;
- 它不替代 encoding/json 的完整反序列化能力(如 time.Time、自定义 UnmarshalJSON),适用于结构已知、性能敏感的 ETL 或网关场景;
- 生产环境建议封装为 func ParseJSONStream(r io.Reader, fn func(interface{}) error) error,通过回调处理每个对象,避免累积内存。
总结:当标准 json.Decoder 不适用时,转向底层扫描器是平衡性能与可控性的最优解。它把解析逻辑显式暴露给开发者,既规避了正则/字符串分割的脆弱性,又比 fork 标准库解码器更轻量、更易维护。










