应使用 json.Decoder 流式解析大体积 JSON 数组,先读取 ‘[‘ 开头,再循环 Decode 每个元素,避免整数组加载内存;注意数字为 float64、字段存在性检查及结构固定时优先用 struct。

用 json.Decoder 处理大体积 JSON 数组流
直接用 json.Unmarshal 解析整个字符串到 []interface{} 会把全部数据加载进内存,对几百 MB 的 JSON 数组极易 OOM。真正流式处理必须用 json.Decoder,它能边读边解析,不缓存完整结构。
关键前提是:JSON 必须是顶层为数组([...]),且元素结构一致或可容忍动态类型。如果源是文件、HTTP 响应体或管道,直接传 io.Reader 给 json.NewDecoder 即可。
- 不要先
bytes.NewReader([]byte(jsonStr))再解码——这又把整串载入内存了 - 若只有字符串变量(非流),至少用
strings.NewReader(jsonStr)避免额外字节拷贝 -
json.Decoder默认跳过空白,兼容换行/缩进,无需预处理
逐个解析数组元素而不是一次性展开
不能指望 decoder.Decode(&slice) 把整个数组塞进一个 []interface{}——它只解一个顶层值。对数组,得手动推进:先确认开头是 [,再循环调 Decode 直到遇到 ] 或 EOF。
典型错误是写成 for decoder.More() { ... }——More() 只对对象/数组内部有效,且需配合 Token() 手动状态机,极易出错。更稳的方式是用 Delim 判断边界:
立即学习“go语言免费学习笔记(深入)”;
dec := json.NewDecoder(r)
tok, _ := dec.Token() // 读取 '['
if delim, ok := tok.(json.Delim); !ok || delim != '[' {
return errors.New("expected array start")
}
for dec.More() {
var item interface{}
if err := dec.Decode(&item); err != nil {
return err
}
// 处理 item,例如 append 到切片、写入数据库、转发到 channel
}
// 此时自动跳过结尾的 ']'
处理嵌套结构或字段缺失时的类型安全问题
interface{} 在反序列化后是 map[string]interface{}、[]interface{}、float64、string、bool、nil 这六种,没有 int/int64 区分——JSON 数字一律是 float64。直接断言 item.(map[string]interface{})["id"].(int) 会 panic。
- 取数字字段:先断言为
float64,再转int64或int(注意精度) - 取字符串字段:用
value, ok := item.(map[string]interface{})["name"].(string),永远检查ok - 字段可能不存在?用
map[string]interface{}的原生 key 检查,别依赖json.RawMessage预留 - 如果结构其实固定,优先定义 struct + 字段 tag,仅在字段动态增减时才用
interface{}
性能和边界情况提醒
流式解析快,但每个 Decode(&item) 都有反射开销。如果元素量级超百万,且结构稳定,考虑用 jsoniter 或手写 UnmarshalJSON 方法替代通用 interface{}。
- JSON 中混入注释(如
//或/* */)会导致invalid character错误——标准encoding/json不支持注释 - 超长字符串或深层嵌套可能触发
maxDepth限制(默认 10000),需用dec.DisallowUnknownFields()或自定义Decoder实例设置 - 遇到非法 UTF-8 字节时默认返回 error;若需容错,得在读取前用
bytes.ReplaceAll清洗或改用第三方库
流式解析的核心约束就一条:你得自己管理数组边界和元素生命周期。没人替你缓存整个切片,但换来的是可控的内存水位和实时处理能力。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/shoujipingce/123872.html