Go json.Decoder 连续 JSON 怎么读:Decode 循环、EOF 与尾部数据校验
日志采集接口收到的经常不是单个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、截断和多值就各自有清晰含义,排查时也不必靠猜。
Go 构建 AI 评测样本 CLI:校验 JSONL、抽样回放并生成通过率报告
- 上一篇
- Go 构建 AI 评测样本 CLI:校验 JSONL、抽样回放并生成通过率报告
- 下一篇
- Go HTTP PATCH 怎么区分字段缺失和显式置空:指针字段、null 语义与兼容返回
-
- Golang · Go问答 | 26分钟前 | go · CGO · 构建排错 · Go CGO 头文件 CGO_ENABLED
- Go build 找不到 cgo 头文件时先检查什么
- 393浏览 收藏
-
- Golang · Go问答 | 1小时前 | 依赖管理 · go · Go Modules · replace go.mod go mod tidy
- Go mod tidy 为什么会移除本地 replace 依赖
- 266浏览 收藏
-
- Golang · Go问答 | 1小时前 | go · go.mod · 模块依赖 · go mod tidy Go indirect依赖 Go模块依赖排查
- Go mod tidy 后为什么多出 indirect 依赖
- 424浏览 收藏
-
- Golang · Go问答 | 17小时前 | 结构体 · JSON · Marshal · Go问答 · UnmarshalJSON · Go encoding/json omitempty json.UnmarshalJSON 零值结构体
- Go json.UnmarshalJSON omitempty 为什么不会隐藏零值结构体
- 261浏览 收藏
-
- Golang · Go问答 | 17小时前 | 数据结构 · JSON · go · RawMessage Go JSON json.UnmarshalJSON
- Go json.UnmarshalJSON RawMessage 适合延迟解析哪些字段
- 420浏览 收藏
-
- Golang · Go问答 | 17小时前 |
- Go json.UnmarshalJSON 自定义方法为什么会递归
- 464浏览 收藏
-
- Golang · Go问答 | 18小时前 | go · 代理 · http.Transport · RoundTripper ·
- Go http.Transport 自定义 RoundTripper 如何保留默认代理
- 401浏览 收藏
-
- Golang · Go问答 | 18小时前 |
- Go http.Transport 禁用 KeepAlives 后为什么吞吐下降
- 350浏览 收藏
-
- Golang · Go问答 | 18小时前 | 网络编程 · HTTP · go · http.Transport IdleConnTimeout Go HTTP客户端
- Go http.Transport 复用连接时 IdleConnTimeout 怎么设置
- 396浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 97次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 28次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 252次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 180次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 113次使用
-
- 接口返回 200 但前端仍报错怎么办:从响应格式到跨域一步步排查
- 2026-06-14 332浏览
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- golang生成JSON以及解析JSON
- 2023-01-17 329浏览
-
- Go如何实现json字符串与各类struct相互转换
- 2023-01-07 377浏览
-
- Go代码规范错误处理示例经验总结
- 2022-12-23 278浏览

