go语言流式写json必须用json.encoder,因json.marshal系列函数只能全量生成[]byte,无法边生成边写入;直接循环marshal+writefile会破坏json结构、性能差且非真正流式。

Go 语言没有“流式写入 JSON 数组”的内置语法糖,json.Marshal 和 json.MarshalIndent 都是全量生成 []byte,无法边生成边写入。真要流式写 JSON(比如逐条写入日志、导出海量记录),必须绕过 Marshal,直接用 json.Encoder。
为什么不能用 json.Marshal + os.WriteFile 做“流式”
很多人试图循环调用 json.Marshal 然后 os.WriteFile 追加——这不仅慢(每次开文件、覆盖权限、刷磁盘),还破坏 JSON 结构:你得到的是一堆独立 JSON 对象拼在一起,不是合法数组,也不是 NDJSON(缺换行)。
-
os.WriteFile每次都是覆盖写,不是追加;想追加得用os.OpenFile(..., os.O_APPEND|os.O_WRONLY|os.O_CREATE) - 即使追加,结果是
{"a":1}{"b":2}{"c":3},这不是有效 JSON,json.Unmarshal会报invalid character '{' after top-level value -
json.MarshalIndent的缩进逻辑依赖完整结构,单个对象加缩进没意义,还拖慢性能
正确做法:用 json.Encoder 写 NDJSON 或带边界的 JSON 数组
json.Encoder 绑定到 *os.File 或任意 io.Writer,每次 Encode() 输出一个合法 JSON 值,并自动加换行(NDJSON 标准)。它不缓存、不拼接、不占额外内存,就是纯粹的流式写入。
- 写 NDJSON(推荐用于日志、ETL、API 流):
enc := json.NewEncoder(file); enc.Encode(item),每条一行,天然可逐行解析 - 写 JSON 数组(如
[{},{},{}]):需手动输出[和],中间用enc.Encode()写元素,并在首次后补逗号 —— 但更稳妥的做法是先写[,再循环写item,最后写],靠io.WriteString控制边界 - 别复用
json.Encoder跨 goroutine:它不是并发安全的;高并发写同一文件,应加锁或用 channel 归集后单协程写
常见错误:忘记关闭文件或忽略 Encoder 错误
json.Encoder.Encode() 返回 error,但很多人只检查一次或干脆忽略。一旦磁盘满、管道断开、编码器内部缓冲区溢出(极少见),后续所有 Encode() 都会静默失败或 panic。
- 必须每次检查:
if err := enc.Encode(item); err != nil { return err } - 写完记得
file.Close();如果用os.Create(),它内部已设O_TRUNC,旧内容会被清空 - 若写入网络连接(如 HTTP response body),别在
Encode()后再调WriteHeader(),顺序错会导致http: superfluous response.WriteHeader call
中文字段乱码?不是 Encoder 的问题
json.Encoder 输出默认 UTF-8,只要你的 Go 字符串本身是 UTF-8(所有 string 字面量都是),就不会出现 \uXXXX。乱码通常来自:
- 原始数据里混了 GBK 字节,被当成 UTF-8 解释 → 应在读取源头转码,不是 Encoder 层能解决的
- 用
fmt.Fprintf(w, "%s", string(b))包裹json.Marshal结果 → 多余转义,直接w.Write(b)或用Encoder - 编辑器/终端没设 UTF-8 编码 → 和代码无关,查终端 locale 或 VS Code 文件编码设置
真正难的是边界控制和错误传播——Encoder 本身很薄,但怎么组织开头、分隔、结尾、重试、回滚,得自己兜底。别指望它自动帮你补括号或处理部分失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











