Go json.Decoder 连续 JSON 怎么读:Decode 循环、EOF 与尾部数据校验
来源:17golang原创
时间:2026-07-27 11:08:01 458浏览 收藏
日志采集接口收到的经常不是单个JSON,而是好几个紧挨着拼接在一起的对象:{"id":1}{"id":2}。这时候如果直接用 json.Unmarshal 去解析,会因为尾部还剩未处理的内容直接报错;要是把整个请求体一次性读到内存里手动切分,又很容易把字符串字段里藏的大括号误当成两个对象的分界点。更稳妥的方案是直接交给 json.Decoder 处理,每次读取一个完整的JSON值,直到真的读到 io.EOF 为止。
要点速览
json.Decoder.Decode每次消费一个 JSON 值,值之间可以只有空白字符,不要求换行。- 循环结束时的
io.EOF是正常收尾;语法错误、类型错误和超长请求体要分别处理。 - 若接口只允许“一个或多个 JSON 值”,读完后还要确认没有第二个非空值,不能只看第一次成功。
- HTTP 请求体应先套
http.MaxBytesReader,并在所有路径关闭r.Body。
先确认输入到底是单个 JSON 还是 JSON 流
这两个格式看起来只差一个额外的对象,但对应的接口契约完全不同。单个 JSON 通常长这样:
{"id":1,"event":"login"}
连续 JSON 则可能来自日志管道、批量推送或长连接无分隔帧场景:
{"id":1,"event":"login"}
{"id":2,"event":"logout"}
如果服务端只允许一个对象,收到第二个对象应报错;如果接口本来就是流式协议,才应该继续调用 Decode。这里先定清边界,后面的 EOF 判断才不会把脏数据当成合法输入结束。

用 Decode 循环按完整值推进读取位置
下面的示例把每个 JSON 对象放进同一个结构体。Decoder 会维护内部缓冲,不需要自己查找大括号,也不会因为字符串字段里出现 { 或 } 就提前截断。
type Event struct {
ID int `json:"id"`
Event string `json:"event"`
}
func readEvents(r io.Reader) ([]Event, error) {
dec := json.NewDecoder(r)
var events []Event
for {
var item Event
err := dec.Decode(&item)
if errors.Is(err, io.EOF) {
return events, nil
}
if err != nil {
return nil, fmt.Errorf("decode event: %w", err)
}
events = append(events, item)
}
}
这个函数的关键不是“循环”,而是错误分层:只有 io.EOF 表示输入正常结束。空白、换行和多个值之间的缩进都会被解码器跳过;半截对象则会返回语法错误,调用方不应把已经追加的结果直接当成完整批次。
类型不对时,为什么不能只检查 EOF
假设输入里的 id 变成字符串,Decode 可能返回类型错误。即使某些字段已经写进 item,这个对象也不应加入结果集。示例中只有没有错误时才 append,正是为了避免半成品混入后续处理。
单值接口要额外检查第二次 Decode
很多“解析成功但接口仍不安全”的问题,都出在只调用了一次 Decode。如果契约是一个 JSON 对象,第二次读取应当只能得到 io.EOF:
func readOne(r io.Reader) (Event, error) {
dec := json.NewDecoder(r)
var item Event
if err := dec.Decode(&item); err != nil {
return Event{}, fmt.Errorf("first value: %w", err)
}
var extra json.RawMessage
if err := dec.Decode(&extra); !errors.Is(err, io.EOF) {
if err == nil {
return Event{}, errors.New("more than one JSON value")
}
return Event{}, fmt.Errorf("trailing data: %w", err)
}
return item, nil
}
第二个 JSON 值前即使有换行和空格,也会被跳过,所以这种写法不会把格式化空白误报成脏数据。若协议允许注释、逗号或自定义分隔符,则不能把它们直接当成标准 JSON 输入,应在协议层先作明确约定。

HTTP 请求体还要加上大小和关闭边界
流式读取不等于无限读取。对外接口可以先把请求体限制在业务允许的大小,再交给解码函数:
func eventHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
limited := http.MaxBytesReader(w, r.Body, 1
MaxBytesReader 把超限变成可识别的读取错误,避免客户端用一个永不结束的大请求拖住处理协程。实际项目里还应配合请求超时、Content-Type 检查和每批事件数量上限;这些是不同的边界,不能只靠 JSON 语法校验代替。
| 现象 | 更可能的含义 | 处理动作 |
|---|---|---|
| 第一次 Decode 立即 EOF | 请求体为空 | 按业务返回缺少输入 |
| 第一次成功,第二次成功 | 单值接口收到多个值 | 拒绝尾部数据,检查调用方 |
| 返回 unexpected EOF | JSON 在传输中被截断 | 记录请求大小和连接超时 |
| 读取超出限制 | 请求体超过 1 MiB | 返回 413 或业务约定的错误 |
常见问题:Decode 循环和 EOF
JSON 值之间必须换行吗?
不必须。标准 JSON 空白包括空格、换行、回车和制表符,两个完整值只要能被解码器区分即可。
为什么不用 strings.Split 切 JSON?
字符串值、转义字符和嵌套对象都可能包含看似边界的字符,按文本切分无法可靠判断 JSON 结构。应使用解码器或上游提供明确的行协议。
读到 EOF 后还能复用同一个 Decoder 吗?
通常把 EOF 视为输入结束即可,不要继续等待同一个已结束的有限 Reader。长连接场景则应由外层协议决定下一帧何时到达。
已经解析出前几个对象,后面出错能否继续入库?
这取决于批处理是否允许部分成功。订单、账务等场景更适合先落临时批次,全部通过后再提交;日志类场景可以记录失败位置后继续,但必须让调用方知道结果不完整。
把 EOF 当作协议状态,而不是异常字符串
连续 JSON 的可靠读取,核心是让输入格式、解码进度和业务提交边界互相对应:流式协议循环消费,每次成功只提交一个完整值;单值接口再读一次确认尾部;HTTP 层补上大小、超时和关闭。这样日志里的 EOF、截断和多值就各自有清晰含义,排查时也不必靠猜。
-
226 收藏
-
Golang · Go问答 | 1星期前 | 错误处理 · go · 性能 · bytes.Buffer · Go 1.26 · io.EOF 版本迁移 Go 1.26 bytes.Buffer.Peek 缓冲区预览428 收藏
-
488 收藏
-
160 收藏
-
158 收藏
-
Golang · Go问答 | 1星期前 | golang · 连接池 · database/sql · Go问答 · 数据库事务 · 连接池 事务 DBStats rows.Close Go database/sql374 收藏
-
271 收藏
-
Golang · Go问答 | 1星期前 | golang · 错误处理 · 泛型 · Go问答 · Go 1.26 · errors.As Go问答 Go 1.26 errors.AsType 泛型错误处理255 收藏
-
187 收藏
-
382 收藏
-
158 收藏
-
279 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习