当前位置:首页 > 文章列表 > Golang > Go问答 > jsonDecoder读完对象后还有内容怎么办

jsonDecoder读完对象后还有内容怎么办

来源:17golang原创 2026-11-15 07:00:09 0浏览 收藏

如果接口约定请求体只能包含一个 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 是否为空,也不能证明底层输入已经结束。

Reader与Decoder缓冲区和JSON值的结构关系
图1:输入边界结构说明图。Decoder 同时关联内部缓冲与底层 Reader,首个值的边界不等于输入结束;图中连线不是执行流程。

第二次 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”和“超出限额”的机制。大小限制也不能替代读超时:客户端一直不结束请求体时,结束检查可能继续等待,应由服务器读取超时策略约束。

单对象契约的错误分类和业务提交条件
图2:单对象契约结构说明图。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。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
jsonNumber被转成浮点数怎么办jsonNumber被转成浮点数怎么办
上一篇
jsonNumber被转成浮点数怎么办
omitempty无法隐藏零值结构体怎么办
下一篇
omitempty无法隐藏零值结构体怎么办
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    1034次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    1033次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1175次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1139次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    979次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码