jsonDecoder读完对象后还有内容怎么办
来源:17golang原创
时间:2026-11-15 07:00:09 169浏览 收藏
如果接口约定请求体只能包含一个 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。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
394 收藏
-
479 收藏
-
399 收藏
-
349 收藏
-
287 收藏
-
423 收藏
-
183 收藏
-
102 收藏
-
422 收藏
-
483 收藏
-
289 收藏
-
298 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习