json.Decoder比json.Unmarshal更省内存,因其绑定io.Reader按需解析单个JSON值后立即释放,内存峰值仅KB级;而Unmarshal需先全量加载[]byte,易OOM。

json.Decoder 为什么比 json.Unmarshal 更省内存
因为 json.Unmarshal 必须接收完整 []byte,你得先调用 os.ReadFile 或 io.ReadAll 把整个 JSON 加载进内存;而 json.NewDecoder 绑定在任意 io.Reader 上,内部用缓冲 + 状态机,每次只解析一个 JSON 值(比如一个 {} 或 "string"),内存稳定在 KB 级别。
常见误用:body, _ := io.ReadAll(resp.Body); json.Unmarshal(body, &v)——这完全绕过了流式能力,和 Python 里 json.load(open()) 一样危险。
- 真正流式起点:传
*os.File、http.Response.Body、bufio.Reader给json.NewDecoder - 别用
bytes.NewReader([]byte{})包超大字节数组——等于没省内存,只是换了个 API 调用方式 - HTTP 场景下若上游带 gzip,必须先用
gzip.NewReader(resp.Body)包一层再传给 decoder
顶层是 JSON 数组时怎么避免 OOM
写 dec.Decode(&[]T{}) 会试图把整个数组解成 Go slice,又爆内存;想逐个解析,必须手动跳过 [,再靠 dec.More() 控制循环边界。
核心逻辑三步:
立即学习“go语言免费学习笔记(深入)”;
将一个或多个视频上传至YouTube。当用户想“上传到YouTube”“发YouTube”“批量上传”“upload to YouTube”“post videos to YouTube”或发布到YouTube时使用此技能。
- 第一步:调
dec.Token()检查并跳过'['——如果返回不是json.Delim('['),说明格式不对或开头有垃圾字符 - 第二步:进入
for dec.More() { }循环——dec.More()内部会自动识别逗号分隔和结尾']' - 第三步:每次循环内调
dec.Decode(&item),item可以是map[string]interface{}、自定义struct或json.RawMessage
高频错误:invalid character '}' after top-level value 不是语法错,而是 decoder 已退出数组上下文还在继续 Decode()。典型诱因是没在 dec.More() 为 false 后及时 break;调试技巧:循环开头加 if !dec.More() { break },比依赖 io.EOF 更可靠。
字段动态或结构不一致时怎么保活
日志、ETL 数据常混着不同 "type" 的对象,硬写 struct 容易 panic。此时应把不确定字段声明为 json.RawMessage,延迟解析到真正需要时。
-
json.RawMessage是字节切片引用,不拷贝原始数据,内存友好 - 不能用于
map的 value 类型(会 panic),只能用于struct字段或slice元素 - 若字段名不固定(如动态 key),必须用
map[string]json.RawMessage,但要检查 key 长度,避免恶意超长 key 耗尽内存 -
Decoder.DisallowUnknownFields()在流式场景下慎用——它会在遇到未知字段时立即报错,而很多真实 API 返回字段是渐进式增加的
替换特定字段值要不要全量解码
如果只改 "title" 这类字段的字符串值,没必要反序列化整个结构体。标准库 Decoder.Token() 太高层,控制粒度不够;推荐用 megajson/scanner 这类底层 tokenizer。
- 它提供
TSTRING、TCOLON、TLEFTBRACE等 token 类型,允许你精准定位"title"键后紧跟的字符串值 - 原地替换时用
bytes.Buffer缓冲输出,配合io.TeeReader实现边读边写 - 注意:标准库 decoder 对嵌套深、字段爆炸(如含 base64 图片)的 JSON 容易卡住,
RawMessage+ 按需解析才是可控方案
最易被忽略的是:即使用了流式解析,后续环节(比如把 json.RawMessage 再传给 json.Unmarshal)仍可能触发二次内存分配。真正控住内存,得从输入源头到最终消费全程保持“按需”意识。

















