json.Decoder能处理超大JSON因其流式解析、内存恒定;json.Unmarshal需全量加载致OOM。Decoder需手动循环解析数组,注意导出字段、错误处理及reader状态维护。

为什么 json.Decoder 能处理超大 JSON,而 json.Unmarshal 会爆内存
因为 json.Unmarshal 必须把整个 JSON 字符串一次性读进内存再解析,哪怕只是想取其中几个字段;json.Decoder 是边读边解析的流式处理器,底层可接 *os.File、net.Conn 或任意 io.Reader,解析完一个值就释放对应内存。
常见错误现象:runtime: out of memory 或 GC 频繁卡顿,尤其在解析几百 MB 的 JSON 数组时。
- 使用场景:日志文件(每行一个 JSON 对象)、导出数据(单个大 JSON 数组)、HTTP 响应体流式消费
- 性能影响:
json.Decoder内存占用基本稳定在几 KB ~ 几百 KB,与文件大小无关;json.Unmarshal占用 ≈ JSON 字符串长度 + 解析开销 - 兼容性无差异:两者都遵循 Go 的 struct tag 规则(如
`json:"name"`),但Decoder不支持直接解析到 interface{} 的深层嵌套(需配合RawMessage)
json.Decoder 解析 JSON 数组时必须手动循环
它不会自动识别并遍历数组元素——你得自己写 for 循环,每次调用 Decode 拿一个元素。这是最常漏掉的逻辑,导致只读了第一个对象就退出。
典型错误:用 decoder.Decode(&v) 试图一次性解一个大数组,结果只拿到数组第一个元素,后续数据被丢弃或报 invalid character '}' after top-level value。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:先跳过开头的
[(可选),然后用for循环反复调用Decode,直到返回io.EOF - 注意
Decode会自动跳过空白符和逗号,所以数组元素间有换行、缩进完全不影响 - 如果数组里混了不同结构的对象,建议用
json.RawMessage先暂存,再按需二次解析
var item MyStruct
for {
if err := decoder.Decode(&item); err == io.EOF {
break
} else if err != nil {
log.Fatal(err) // 注意:这里不能忽略非 EOF 错误
}
// 处理 item
}
遇到 invalid character 'x' looking for beginning of value 怎么定位
这通常不是 JSON 格式问题,而是 Decoder 读到了不该读的位置——比如上一次 Decode 失败后没清空缓冲,或输入流被意外截断/复用。
真实高频原因:把同一个 io.Reader(比如 *bytes.Reader)重复传给多个 json.Decoder,或在错误处理中忘了检查 err 就继续 Decode。
- 调试建议:用
decoder.More()判断是否还有未解析内容(仅对数组/对象顶层有效) - 更可靠的方式:包装原始 reader,记录已读字节数,出错时打印附近 50 字符上下文
- 别依赖
strings.TrimSpace预处理整个文件——它又把全文加载进内存了,违背流式初衷
struct 字段没被赋值?检查 tag 和首字母大小写
Go 的 json 包只认导出字段(首字母大写),且严格匹配 tag 名。小写字母开头的字段无论有没有 json: tag 都不会被设值,也不会报错,静默忽略。
容易踩的坑:定义了 type User struct { name string `json:"name"` },结果 name 始终是零值,debug 半天才发现是没导出。
- 必须写成
Name string `json:"name"`(首字母大写 + tag 小写) - 如果 JSON 字段名含下划线(如
user_id),tag 必须显式写为`json:"user_id"`,否则默认映射为UserId→user_id不匹配 - 空字符串、null 值默认会覆盖字段为零值;如需区分“未提供”和“显式 null”,用指针类型(如
*string)或sql.NullString










