应使用json.decoder配合dec.more()和dec.token()手动处理数组边界,先token()跳过'[',再for dec.more()循环decode(&item),避免json.unmarshal或decode(&[]t)全量加载导致oom。

直接用 json.Decoder 配合 dec.More() 和 dec.Token() 手动跳过数组边界,是唯一能稳定处理 GB 级 JSON 数组而不 OOM 的方式。别指望 json.Unmarshal 或 decoder.Decode(&[]T{}) —— 它们都会把整个数组加载进内存。
为什么不能直接 decoder.Decode(&slice)?
因为 json.Decode() 把整个 JSON 数组(如 [{"a":1},{"b":2}])当做一个顶层值解析,目标变量必须是切片指针(*[]T),但解析过程仍需一次性构建全部元素并分配底层数组内存。文件越大,OOM 越快。
- 现象:
runtime: out of memory或 GC 频繁卡顿,哪怕只取前 10 条 - 本质:标准库不提供“跳过数组容器、只逐个消费成员”的自动模式
- 替代思路:放弃“一次解整个结构”,改用“状态机推进 + 每次解一个值”
正确解析 JSON 数组的三步法
核心是先确认进入数组上下文,再靠 dec.More() 控制循环,每次 Decode() 一个独立对象。这要求输入流必须合法且无 BOM/空行干扰。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 第一步:调
dec.Token()检查并跳过'['—— 如果返回不是json.Delim('['),说明格式不对或开头有垃圾字符 - 第二步:进入
for dec.More() { ... }循环 ——dec.More()内部会自动识别逗号分隔和结尾']' - 第三步:每次循环内调
dec.Decode(&item),item可以是map[string]interface{}、自定义 struct 或json.RawMessage - 注意:
dec.More()返回false不代表错误,可能是刚读完最后一个元素;下一次dec.Token()会返回']',此时应跳出循环
遇到 invalid character '}' after top-level value 怎么办
这不是 JSON 语法错,而是 decoder 已经退出数组上下文还在继续 Decode()。典型诱因是没在 dec.More() 为 false 后及时 break,导致第二次 Decode 尝试从 ] 后读取,自然失败。
- 高频原因:循环条件写成
for { ... if err == io.EOF { break } },漏了dec.More()判断 - 调试技巧:在循环开头加
if !dec.More() { break },比依赖io.EOF更可靠 - 安全兜底:在
dec.Decode()前加if !dec.More() { break },避免多读一帧 - 别用
strings.TrimSpace预处理整个文件——它又把全文读进内存了
字段动态或结构不一致时怎么保活
日志、ETL 数据常混着不同 "type" 的对象,硬写 struct 容易 panic。此时应把不确定字段声明为 json.RawMessage,延迟解析到真正需要时。
- 示例:
Metadata json.RawMessage `json:"metadata"`,后续按需json.Unmarshal(raw, &v) - 优势:跳过类型校验,避免单条脏数据导致整条解析失败
- 注意:
json.RawMessage保存的是原始字节切片,引用的是底层 reader 缓冲区 —— 如果 reader 被复用或提前关闭,内容可能被覆盖 - 更稳做法:解析后立刻拷贝
bytes.Copy或转成string,尤其在 goroutine 中处理时
最易被忽略的是 dec.More() 的作用域限制:它只在已进入数组或对象内部时有效。如果第一次 Token() 没吃掉 '[',或中间某次 Decode() 出错没清状态,More() 就会一直返回 false,循环直接跳过——你甚至收不到任何错误提示。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










