Go Decoder 连续读取遇到 EOF 的边界判断
来源:17golang原创
时间:2026-10-28 15:13:39 269浏览 收藏
连续处理 JSON 消息时,json.Decoder 返回 io.EOF 不代表上一条 JSON 有问题,而是当前输入里已经没有下一个完整的顶层值。正确做法是把它当作“正常结束”分支;只要返回的是语法错误、类型错误或底层读取错误,就应当保留并上报,不能用一个宽泛的 EOF 判断把故障吞掉。
官方地址:https://pkg.go.dev/encoding/json#Decoder
在空白分隔的 JSON 流中,循环调用Decode;err == nil表示成功得到一个值,err == io.EOF表示流结束,其它错误都不是“读完了”。
Decode 返回 EOF 到底说明了什么
Decoder 面向的是输入流,而不是只接收一个 JSON 文档。每次 Decode 读取下一个 JSON 值,因此下面这样的输入可以被连续读取:
{"id":1}
{"id":2}
{"id":3}
前三次调用分别得到三个对象,第四次调用才会在已经跳过尾部空白后得到 io.EOF。空输入或只包含空白的输入也没有可交付的值,第一次调用就可能返回 EOF;这与“收到一个空对象”不同,空对象应写成 {} 并成功解码。
这里的关键是“没有下一个值”。它不是“底层 Reader 恰好被调用了一次并返回 EOF”,因为官方文档明确说明 Decoder 有自己的缓冲,可能会从 Reader 多读取一些数据。业务判断应围绕 Decode 的结果,而不要根据 Reader 的调用次数猜测消息是否结束。

连续 Decode 应该怎样写分支
最小可靠循环可以把三种结果分开:成功继续处理,EOF 正常收尾,其它错误立即返回。示例中的结构体只是演示目标类型,实际项目可换成事件或请求对象。
package main
import (
"encoding/json"
"fmt"
"io"
"strings"
)
type Event struct {
ID int `json:"id"`
Kind string `json:"kind"`
}
func readEvents(input io.Reader) ([]Event, error) {
dec := json.NewDecoder(input)
var events []Event
for {
var event Event
err := dec.Decode(&event)
switch {
case err == nil:
// 成功拿到一个完整值,只有此时才把目标对象加入结果。
events = append(events, event)
case err == io.EOF:
// 没有下一个顶层值是正常收尾,不要把它当成失败。
return events, nil
default:
// 语法、类型或 Reader 错误都要保留,避免静默丢消息。
return nil, fmt.Errorf("decode event: %w", err)
}
}
}
func main() {
input := strings.NewReader(`{"id":1,"kind":"created"}
{"id":2,"kind":"updated"}`)
events, err := readEvents(input)
if err != nil {
panic(err)
}
fmt.Println(len(events)) // 这里会得到 2,EOF 只负责结束循环。
}
不要只写 if err != nil { break }:这样会把半截 JSON、字段类型不匹配,甚至 Reader 的真实故障都伪装成“读完了”。同样,也不要在每次循环前复用上一次已经填充的对象而不清理状态;把新变量放在循环体内,可以避免失败时误用旧值。
空白、截断和类型错误要分开
判断 EOF 时最容易混淆的是“输入没有内容”和“输入内容不完整”。尾部换行、空格属于合法的结束留白,Decode 在跳过它们后返回 EOF。相反,输入如果在 JSON 值中间结束,例如 {"id":,解码器会报告不完整输入相关的错误,而不是把它当作一个正常的流结束。
还有三类错误需要继续向上返回:
- JSON 语法错误:引号、逗号、括号等结构损坏,说明数据本身不可解析。
- 目标类型错误:例如把字符串放入
int字段,值可能已部分写入目标对象,但本次消息不能按成功处理。 - 底层读取错误:网络连接或文件读取失败,不等于业务上没有下一条消息。
如果消息协议要求“至少收到一条记录”,可以在循环结束后检查 len(events);如果空流合法,就让空切片和 nil 错误表示正常结果。这个约束属于业务层,不应通过修改 EOF 判断来表达。
数组内部和 token 级读取的边界
当输入是一个 JSON 数组时,通常应先读取开括号,再用 More 判断数组中是否还有元素,最后读取闭括号。此时数组内部的“没有更多元素”不是靠把 EOF 当作数组结束,而是由 JSON 的结构分隔符表达。
func readArray(input io.Reader) ([]Event, error) {
dec := json.NewDecoder(input)
var events []Event
token, err := dec.Token()
if err != nil {
return nil, fmt.Errorf("read array start: %w", err)
}
if delim, ok := token.(json.Delim); !ok || delim != '[' {
return nil, fmt.Errorf("expected array start, got %v", token)
}
for dec.More() {
var event Event
if err := dec.Decode(&event); err != nil {
// 数组未闭合或元素损坏属于输入错误,不当作正常结束。
return nil, fmt.Errorf("decode array item: %w", err)
}
events = append(events, event)
}
if _, err := dec.Token(); err != nil {
// 读取闭括号,确保数组结构确实结束。
return nil, fmt.Errorf("read array end: %w", err)
}
return events, nil
}
需要逐个查看字段或分隔符时再使用 Token;它在输入流末尾明确返回 nil, io.EOF。如果一个 Decoder 后面还要接着读取非 JSON 数据,要注意它可能已经预读,官方提供的 Buffered 只能在下一次 Decode 或 Token 前使用,不能把底层 Reader 的当前位置当作精确边界。

最后检查这三个结果
连续读取的判断可以压缩成三句话:nil 是“本条值已成功交付”,io.EOF 是“没有下一条值且允许收尾”,其它错误是“输入或目标处理失败”。只要把这三个结果分开,空输入、尾部空白、截断 JSON 和字段类型错误就不会互相伪装。
- 多条顶层 JSON:循环 Decode,EOF 收尾。
- 单个数组:用 More 表达数组元素边界,用 Token 读取括号。
- 流式协议:把 EOF 是否允许作为业务约束放在循环外,不能吞掉真实错误。
常见问题
第一次 Decode 就返回 EOF,是不是 Decoder 配置错了?
不一定。输入为空或只有空白时,这是正常结果;如果业务要求必须有一条消息,再由调用方把“零条记录”判为业务错误。
可以用所有非 nil 错误来结束循环吗?
不可以。只有明确识别为 EOF 才能正常结束;语法错误、类型错误和底层错误都应记录或返回。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
389 收藏
-
130 收藏
-
308 收藏
-
146 收藏
-
324 收藏
-
496 收藏
-
438 收藏
-
501 收藏
-
330 收藏
-
479 收藏
-
304 收藏
-
403 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习