应使用 json.NewDecoder 配合 io.Reader 流式解析大 JSON 文件,避免 json.Unmarshal 全量加载导致 OOM;数组需手动跳过容器边界并循环解码;动态字段用 json.RawMessage 而非 interface{};序列化大量数据时用 bytes.Buffer 替代字符串拼接。

别用 json.Unmarshal 加载整个文件
直接 json.Unmarshal 读取大 JSON 文件(尤其是 GB 级)必然 OOM。它要求先把全部内容读进 []byte,再反射解析——内存占用≈文件大小 × 2~3 倍。哪怕只是想取前 10 条,也会全量加载。
正确路径是绑定 io.Reader:用 json.NewDecoder(r),其中 r 可以是 *os.File、http.Response.Body 或任何流式输入源。解码器边读边解析,内存只维持当前 token 和目标结构体实例。
- 绝对不要写
data, _ := os.ReadFile("big.json"); json.Unmarshal(data, &v) - HTTP 场景下,直接传
resp.Body给json.NewDecoder,别先ioutil.ReadAll或io.ReadAll - 如果上游带 gzip,记得用
gzip.NewReader(resp.Body)包一层,再传给json.NewDecoder
数组顶层必须手动跳过容器边界
遇到形如 [{"id":1},{"id":2},...] 的巨型数组,decoder.Decode(&[]T{}) 仍会全量分配底层数组并加载所有元素——这不是流式,是假流式。
真正能控内存的解法是三步手动推进:
立即学习“go语言免费学习笔记(深入)”;
- 调一次
dec.Token(),确认并跳过'['(返回值应为json.Delim('[')) - 进入
for dec.More() { ... }循环,dec.More()内部自动识别逗号分隔和结尾']' - 每次循环内调
dec.Decode(&item),item可是struct、map[string]interface{}或json.RawMessage
漏掉 dec.More() 判断,或循环条件写成 for { if err == io.EOF { break } },就会在读完最后一个元素后多 decode 一次,触发 invalid character '}' after top-level value 错误。
字段不确定时优先用 json.RawMessage
日志、ETL 数据常混着不同 "type" 的对象,硬写 struct 容易 panic 或静默丢字段。比如 "metadata" 有时是空值、有时是字符串、有时是嵌套对象。
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
把这类字段声明为 json.RawMessage,跳过即时解析:
type LogEntry struct {
ID int `json:"id"`
Type string `json:"type"`
Metadata json.RawMessage `json:"metadata"`
}
后续按需处理:json.Unmarshal(entry.Metadata, &target)。这样既避免全量反序列化失败,又防止因类型错位导致整个 item 被丢弃。
- 别用
interface{}接收动态字段——反射开销大,且无法跳过无效字段 -
json.RawMessage是字节切片引用,不拷贝原始数据,内存友好 - 注意:它保留原始 JSON 字节,包括空格和换行;若需干净字符串,得自己 trim 或重新 marshal
序列化大量结构体别拼字符串
生成 NDJSON(每行一个 JSON)时,用 buffer += string(body) + "\n" 是灾难。Go 字符串不可变,每次 += 都分配新底层数组并复制全部历史内容,内存增长呈 O(n²)。
换成 bytes.Buffer:
var buf bytes.Buffer
for _, record := range records {
body, _ := json.Marshal(record)
buf.Write(body)
buf.WriteByte('\n')
}
result := buf.Bytes() // 直接拿 []byte,或 buf.String() 最后转一次
bytes.Buffer 底层是可扩容的 []byte,Write 和 WriteByte 复用同一块内存,均摊写入开销接近 O(1)。上万条记录下,内存峰值能从几百 MB 降到几 MB。
更进一步:如果目标是写文件或 HTTP 响应,直接 io.Copy(writer, &buf) 或 json.NewEncoder(writer).Encode(item),避免中间 []byte 拷贝。

















