jsonDecoder读完对象后还有内容怎么办
如果接口约定请求体只能包含一个 JSON 对象,第一次 json.Decoder.Decode 成功后,应使用同一个 Decoder 再尝试读取一个值:只有返回 io.EOF 才表示后面仅有空白并且输入已经结束。第二次返回 nil 表示还存在另一个完整 JSON 值,其他错误则可能表示尾部损坏、读取失败或超出大小限制,都不应按正常结束接受。
官方地址:https://pkg.go.dev/encoding/json#Decoder.Decode
用输入矩阵建立基线
一个更新标题接口可能收到以下内容。先明确要求是“一个对象”,再比较只调用一次 Decode 和补充结束检查后的行为,才能判断当前实现漏掉了什么。
| 输入内容 | 第一次 Decode | 单对象契约的预期结果 |
|---|---|---|
| 一个有效对象 | 可以成功 | 接受 |
| 对象后跟空格或换行 | 可以成功 | 接受 |
| 对象后再跟另一个对象 | 可以只读到首对象 | 拒绝 |
| 对象后跟 null | 可以只读到首对象 | 拒绝 |
| 对象后跟损坏内容 | 可能先成功 | 拒绝 |
这些是协议与 API 语义对应的预期判断,没有声称在本机执行过测试。问题的基线是漏接收了多少种不符合协议的输入,而不是先假设需要替换 JSON 库或提高解析速度。
区分值边界与流边界
Decoder 面向流,每次 Decode 读取下一个 JSON 值。因此第一个对象结束,不意味着整个 Reader 结束。它还会自行缓冲输入,甚至提前读取超过当前 JSON 值的字节。若直接从原始 Reader 再读取,可能漏掉已经进入 Decoder 缓冲区的内容。
继续使用同一 Decoder,可以让结束检查覆盖它已缓存的部分和底层尚未读取的部分。Buffered 适合需要访问当前缓冲余量的协议场景,但只检查 Buffered 是否为空,也不能证明底层输入已经结束。

第二次 Decode 检查 EOF
下面代码实现一个简单 HTTP 处理器及其解析函数。JSON 对象采用导出字段 Title;示例输入可写为 {"Title":"新标题"}。这里只允许一个非 null 对象,并对未知字段报错,Title 必须非空。DecodeOne 不负责关闭传入 Reader,关闭请求体由处理器负责。
package api
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)
type Update struct {
Title string
}
// DecodeOne 在全部格式检查通过后才返回可供业务使用的数据。
func DecodeOne(r io.Reader) (Update, error) {
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
// 指针目标用于拒绝顶层 null;数组、字符串等也不符合对象类型。
var input *Update
if err := dec.Decode(&input); err != nil {
return Update{}, fmt.Errorf("读取首对象: %w", err)
}
if input == nil {
return Update{}, errors.New("请求体必须为对象")
}
// RawMessage 接受任意第二个值,避免把第二个值的类型错误当成结束。
var extra json.RawMessage
err := dec.Decode(&extra)
if err == nil {
return Update{}, errors.New("请求体只能包含一个对象")
}
if !errors.Is(err, io.EOF) {
return Update{}, fmt.Errorf("读取对象尾部: %w", err)
}
if input.Title == "" {
return Update{}, errors.New("Title 不能为空")
}
return *input, nil
}
func UpdateHandler(w http.ResponseWriter, r *http.Request) {
// 示例限制为 64 KiB,按真实业务选择上限;不要只信 Content-Length。
r.Body = http.MaxBytesReader(w, r.Body, 64
把处理器注册到项目已有路由即可。示例没有实现持久化:204 只表示演示处理器接受了输入,不能当作数据库已更新。业务接入时应在注释指定位置执行操作并处理其错误,再返回成功。
错误判断必须使用 EOF,而不是把所有非 nil 错误都理解成“没有更多内容”。例如尾部只有一个未完成的对象,可能得到 unexpected EOF;它表示缺失输入,不能按正常 EOF 放行。第二个值即使是 null、数字或数组,也违反本例单对象约定,因此用 RawMessage 统一接收检测即可。
处理 HTTP 大小限制与业务提交
MaxBytesReader 在读取超过限额时报告错误,并不是凭 Content-Length 预先判断。第一次解码对象很小,但后面跟着大量空白时,第二次读取仍可能触发限额;错误应保留而不是吞掉。本例使用错误包装并通过 errors.As 识别 MaxBytesError。
简单的 io.LimitReader 会在限额处给出类似输入结束的效果,若只读允许字节数而不检查额外内容,可能把被截掉的尾部误认为不存在。因此 HTTP 场景优先使用能区分“真实 EOF”和“超出限额”的机制。大小限制也不能替代读超时:客户端一直不结束请求体时,结束检查可能继续等待,应由服务器读取超时策略约束。

业务提交位置尤其重要。若解码第一个对象就写数据库,之后才发现尾部无效,返回 400 也无法自动撤回已发生的动作。应先完成首对象、尾部和业务字段检查,再进入业务事务。
说明流式协议及测量方法
如果协议本来允许多个 JSON 值,就应循环 Decode 直到正常 EOF,而不是采用本例的“第二个值即拒绝”规则。NDJSON 还可能要求一行一条记录,需要另外处理行边界。Decoder.More 用于数组或对象内的元素判断,不能替代顶层 Reader 是否结束的检查。
单对象尾部检查并不检查所有协议细节。DisallowUnknownFields 不会自动禁止重复键,也不会强制字段大小写精确匹配;这些要求应单独设计。此处重点是对象之后的剩余输入,不应把它称为全面严格 JSON 校验器。
评估改动时,先对表中各类输入分别测试接受或拒绝,再用相同工具链、对象大小、尾随空白长度和输入比例比较耗时与分配。记录 ns/op、B/op 和 allocs/op 时,应把正常对象、额外对象、损坏尾部拆开观察。第二次读取可能增加扫描成本,也可能等待底层输入结束;没有实测数据就不应承诺吞吐提升。本文未执行压测,不提供虚构结果。
可以改用 json.Unmarshal 吗?
若已经有完整且受限的字节切片,Unmarshal 会要求输入构成一个完整 JSON 值并允许尾随空白。但若先从网络无限量 ReadAll,再调用它,仍没有解决输入大小和等待问题。
第二次 Decode 会修改首对象吗?
示例把第二次结果放进独立 RawMessage,不复用业务对象;无论尾部结果如何,失败都返回空业务值。
尾部只有换行为什么还需要读取?
必须读过这些合法空白并确认真实输入结束,才能判断后面没有第二个值或无效内容。
官方参考:https://pkg.go.dev/encoding/json#Decoder.Decode;请求体限额:https://pkg.go.dev/net/http#MaxBytesReader。
jsonNumber被转成浮点数怎么办
- 上一篇
- jsonNumber被转成浮点数怎么办
- 下一篇
- omitempty无法隐藏零值结构体怎么办
-
- Golang · Go问答 | 11分钟前 |
- 响应体未关闭导致连接池耗尽怎么办
- 394浏览 收藏
-
- Golang · Go问答 | 16分钟前 | JSON · 序列化 · Go问答 · Go MarshalJSON encoding/json/v2 JSON v2 MarshalJSONTo
- jsonv2迁移后自定义Marshal失效怎么办
- 479浏览 收藏
-
- Golang · Go问答 | 17分钟前 |
- jsontext遇到无效Token如何定位
- 399浏览 收藏
-
- Golang · Go问答 | 19分钟前 | 结构体 · JSON · go · Go encoding/json omitempty omitzero
- omitempty无法隐藏零值结构体怎么办
- 349浏览 收藏
-
- Golang · Go问答 | 21分钟前 | 类型转换 · Go问答 · Go encoding/json float64 UseNumber json.Number JSON数字
- jsonNumber被转成浮点数怎么办
- 287浏览 收藏
-
- Golang · Go问答 | 26分钟前 |
- json遇到非法UTF8为什么不报错
- 423浏览 收藏
-
- Golang · Go问答 | 27分钟前 |
- json解码重复键结果异常怎么办
- 183浏览 收藏
-
- Golang · Go问答 | 27分钟前 |
- json字段大小写匹配不符合预期怎么办
- 102浏览 收藏
-
- Golang · Go问答 | 4小时前 |
- Go atomic.Pointer CompareAndSwap 失败后的重读与退避策略
- 422浏览 收藏
-
- Golang · Go问答 | 4小时前 | Go 文档注释 斜体 comment.Parser Words
- Go comment.Parser 的 Words 空字符串为什么仍会改变排版
- 483浏览 收藏
-
- Golang · Go问答 | 4小时前 | Go int64 UNKNOWN constant.Make
- Go constant.Make 传入 int 为什么得到 Unknown
- 289浏览 收藏
-
- Golang · Go问答 | 13小时前 |
- Go utf8.DecodeRuneInString 为什么会返回 RuneError 和 1
- 298浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1034次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1033次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1175次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1139次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 979次使用
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- Go pprof profile duration 参数如何理解
- 2026-11-14 501浏览
-
- Go types.Selection.Type 与 Obj.Type 为什么不总是相同
- 2026-11-10 501浏览
-
- Go flag.FlagSet ContinueOnError 怎么捕获帮助输出和解析错误
- 2026-11-05 501浏览

