json.decoder能边读边解析因其基于io.reader流式处理,按需解析单个json值而不缓存全文;json.unmarshal需一次性加载全部json到内存,处理大文件易oom。

为什么 json.Decoder 能边读边解析,而 json.Unmarshal 会爆内存
因为 json.Unmarshal 要求整个 JSON 数据必须一次性加载进内存,再解析成 Go 结构体;而 json.Decoder 是基于 io.Reader 的流式解析器,它只在需要时读取并解析下一个 JSON 值(比如一个对象或数组元素),不缓存完整输入。
典型踩坑:用 json.Unmarshal 处理几百 MB 的 JSON 数组文件,直接触发 OOM —— 它根本不管你是“一行一个对象”还是“裹成一坨大数组”,一律全吃。
- 适用场景:日志文件(每行一个 JSON)、API 流式响应、ETL 导入、大 JSON 数组逐条消费
- 不适用场景:需要随机访问嵌套字段、依赖完整 schema 校验、或输入根本不是合法 JSON Stream(比如开头缺
[或结尾多逗号) - 性能影响:
Decoder内存占用基本恒定(KB 级),但吞吐略低于批量解析(因少了预分配和批处理优化)
怎么用 json.Decoder 逐行解析 NDJSON(Newline-Delimited JSON)
NDJSON 是最常见 JSON Stream 格式:每行一个独立、合法的 JSON 值(如 {"id":1}、{"id":2})。它天然适配 json.Decoder,无需额外切分。
关键点:别把文件整个读成 []byte 再丢给 Decoder,要传 *os.File 或 bufio.Reader。
file, _ := os.Open("data.ndjson")
defer file.Close()
dec := json.NewDecoder(file)
for {
var item map[string]interface{}
if err := dec.Decode(&item); err == io.EOF {
break
} else if err != nil {
log.Fatal(err) // 注意:这里 err 可能是语法错误,不是 EOF
}
// 处理 item
}
- 错误现象:
invalid character 'N' looking for beginning of value→ 文件开头有 BOM 或空行,用bytes.TrimLeft预处理,或换bufio.Scanner+json.Unmarshal(仅当行数可控) - 参数差异:
Decoder.DisallowUnknownFields()仍生效,但对每行单独校验 - 兼容性:Go 1.1+ 全支持,无额外依赖
怎么解析顶层是 JSON 数组的大文件(如 [{"a":1},{"b":2},...])
这种格式不能直接用 dec.Decode() 循环——第一次调用会试图解析整个数组,又回到内存爆炸的老路。必须手动跳过 [,然后循环解析每个元素,直到遇到 ]。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
本质是利用 Decoder.Token() 逐词法单元推进,绕过完整值解析。
dec := json.NewDecoder(file)
tok, _ := dec.Token() // 应该是 '['
if tok != json.Delim('[') {
log.Fatal("expected [")
}
for dec.More() { // 检查是否还有下一个元素(内部读 token 判断 ',' 或 ']')
var item MyStruct
if err := dec.Decode(&item); err != nil {
log.Fatal(err)
}
// 处理 item
}
- 容易踩的坑:
dec.More()必须在每次Decode()后调用,不能放在 for 条件里写成for dec.More() { ... }—— 否则最后一次Decode()后没调More(),可能漏掉末尾元素或 panic - 使用场景:导出的 MongoDB JSON、PostgreSQL
json_agg结果、某些 REST API 的批量响应 - 性能提示:比 NDJSON 略慢(多了 delim 判断),但内存仍是常量级
Decoder 解析失败时,怎么定位哪一行出问题
json.Decoder 默认不暴露行号,错误信息像 invalid character '}' after top-level value,完全看不出位置。得靠包装 io.Reader 自己记偏移。
最轻量做法:用 bufio.Reader + Scanner 读行,每行单独 json.Unmarshal;适合 NDJSON 且单行不超几 MB。
更通用方案:用 json.RawMessage 配合 Decoder.Token() 手动推进,出错时用 dec.InputOffset() 查字节偏移,再换算成行号(需自己维护换行计数)。
- 关键函数:
dec.InputOffset()返回当前解析位置的字节偏移,不是行号 - 常见错误:
invalid character '}' after top-level value→ 上一行 JSON 缺少逗号或多了逗号;unexpected end of JSON input→ 最后一行不完整或被截断 - 注意:
InputOffset()在Decode()报错后才准确,Token()过程中调用可能不准
真正麻烦的是混合格式:既有大数组又有嵌套流,或者 JSON 里混了注释和非标准语法。这时候别硬扛,先用工具(如 jq -c '.[]')预处理成标准 NDJSON。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










