Go Decoder 连续读取遇到 EOF 的边界判断
连续处理 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 才能正常结束;语法错误、类型错误和底层错误都应记录或返回。
Go net/http/pprof 怎样给诊断入口加访问控制
- 上一篇
- Go net/http/pprof 怎样给诊断入口加访问控制
- 下一篇
- Go strconv.IsPrint 判断字符是否适合可打印日志的方案
-
- Golang · Go问答 | 9小时前 | go · 资源管理 · 资源释放 runtime.SetFinalizer runtime.KeepAlive Go finalizer
- Go finalizer 不适合作为资源释放机制的替代设计
- 389浏览 收藏
-
- Golang · Go问答 | 9小时前 |
- Go buildid 与 -s -w 裁剪选项的发布取舍
- 308浏览 收藏
-
- Golang · Go问答 | 9小时前 |
- Go IPv4 mapped IPv6 白名单未命中的地址归一化
- 146浏览 收藏
-
- Golang · Go问答 | 9小时前 |
- Go DNS 在本机与容器不同的 Resolver 配置
- 324浏览 收藏
-
- Golang · Go问答 | 9小时前 |
- Go ldflags 未写入版本信息的链接检查
- 496浏览 收藏
-
- Golang · Go问答 | 9小时前 |
- Go generate 未执行指令的注释与目录检查
- 501浏览 收藏
-
- Golang · Go问答 | 9小时前 |
- Go 自动工具链切换的 GOTOOLCHAIN 判断
- 330浏览 收藏
-
- Golang · Go问答 | 9小时前 | go · use go.work Go workspace
- Go workspace 未使用本地模块的 use 配置
- 479浏览 收藏
-
- Golang · Go问答 | 9小时前 |
- Go replace 仅本机生效的发布配置
- 304浏览 收藏
-
- Golang · Go问答 | 9小时前 | 标准库 · go · Go问答 · Go 资源文件 filepath.EvalSymlinks os.Executable 程序路径
- Go os.Executable 返回路径不稳定时怎样定位程序文件
- 403浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 758次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 755次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 837次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 803次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 635次使用
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- Go generate 未执行指令的注释与目录检查
- 2026-10-28 501浏览
-
- Go RWMutex 写锁等待过久的竞争分析
- 2026-10-28 501浏览
-
- Go os.File.ReadAt 为什么到文件末尾仍返回部分数据
- 2026-10-26 501浏览

