登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go json.Decoder Decode 读到 EOF 是否代表格式错误

来源:17golang原创

时间:2026-09-12 10:53:43 444浏览 收藏

用 Go 的 json.Decoder 读取请求体或连续 JSON 值时,最后一次 Decode 返回 io.EOF,通常只说明输入已经读完,并不代表 JSON 格式错误。真正需要警惕的是 io.ErrUnexpectedEOF 或带有语法位置的解析错误:它们说明数据读到一半就结束,或者内容本身不符合 JSON 语法。

要点速览
  • 单个 JSON 值读成功后,再读不到下一个值,按正常结束处理。
  • 空请求体也可能得到 io.EOF,是否允许要由接口契约决定。
  • 半截对象优先识别 io.ErrUnexpectedEOF,不要把所有错误都改成“格式错误”。
判断标准很简单:EOF 是“没有更多输入”,不是“已有输入格式错误”。但空输入同样会返回 EOF,所以 HTTP 接口还要单独决定空体是否属于参数缺失。

一次 Decode 到底读了什么

Decode 每次尝试从输入中读取下一个完整 JSON 值。输入可以是一个对象,也可以是多个彼此独立的 JSON 值。它不会因为读到了流末尾就自动把 EOF 变成语法错误;调用方需要根据自己要读的是“一个值”还是“整条流”来解释这个结果。

例如输入 {"id":7},第一次调用成功,第二次调用返回 io.EOF。输入只有空白字符时,第一次调用也会返回 EOF,因为解码器没有找到任何值。这两种情况都不是 JSON 语法错误,区别在于业务上是否允许“没有值”。

把 EOF、半截输入和语法错误分开

排查时不要只写 if err != nil。先判断 EOF,再处理其他错误;如果读取的是网络流或上传体,半截 JSON 往往意味着客户端中断、代理截断或请求体不完整。

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "strings"
)

type Payload struct {
    ID int `json:"id"`
}

func decodeOne(input string) {
    var payload Payload
    dec := json.NewDecoder(strings.NewReader(input))

    // EOF 表示没有可供本次 Decode 读取的完整值。
    err := dec.Decode(&payload)
    switch {
    case err == nil:
        fmt.Printf("读取成功: %+v\\n", payload)
    case errors.Is(err, io.EOF):
        // 空字符串和仅含空白的输入都会走到这里。
        fmt.Println("没有 JSON 值")
    case errors.Is(err, io.ErrUnexpectedEOF):
        // 例如对象只收到一半,不能当作正常结束。
        fmt.Println("JSON 输入被截断")
    default:
        // SyntaxError、类型错误等进入业务错误分支。
        fmt.Printf("JSON 无法解析: %v\\n", err)
    }
}

这里的关键不是错误字符串,而是错误类别。errors.Is 适合处理被包装过的 EOF;其余错误应保留原始信息,必要时记录 json.SyntaxError 提供的偏移量,方便定位输入。

连续 JSON 流要在循环末尾接住 EOF

当一个 Reader 中依次放着多个对象时,EOF 是循环的退出信号。成功解析的记录已经有效,不应该因为下一次没有数据就回滚或打印失败日志。

func readStream(r io.Reader) ([]Payload, error) {
    dec := json.NewDecoder(r)
    var result []Payload

    for {
        var item Payload
        // 每轮只取一个完整对象,便于控制内存和错误位置。
        err := dec.Decode(&item)
        if errors.Is(err, io.EOF) {
            // 流正常结束,返回已经收集到的对象。
            return result, nil
        }
        if err != nil {
            // 截断或语法错误都要交给上层决定是否重试。
            return nil, fmt.Errorf("decode JSON stream: %w", err)
        }
        result = append(result, item)
    }
}

如果输入格式要求必须是一个 JSON 数组,就不要把“多个顶层值”误当成合法协议;可以先解码数组,或者在读完第一个值后再尝试一次,确认后面只有空白。不同协议的结束条件不能混用。

Go json.Decoder 连续 JSON 流中完整值、EOF 和截断输入的静态边界关系图
图1:把完整 JSON 值、下一次读取的 EOF 与半截 JSON 的 UnexpectedEOF 放在不同边界中判断。

HTTP 请求体里的 EOF 该怎么处理

在 HTTP handler 中,空 body 返回 EOF 只是读取事实,不自动等价于 400。若接口要求必须提交对象,可以把 EOF 转成“缺少请求体”;若空体有默认语义,则应显式处理默认值。真正的截断或语法错误通常返回 400,并保留服务端日志中的原始错误。

结果常见含义接口处理建议
nil读到一个完整值继续校验字段并执行业务逻辑
io.EOF没有更多值,也可能是空体按接口契约区分正常结束或缺少参数
io.ErrUnexpectedEOF输入在完整值之前结束按坏请求处理,检查客户端和代理链路
其他 error语法、类型或目标结构问题返回明确错误,保留偏移等诊断信息

读取完后仍要关闭请求体:defer r.Body.Close()。如果这是单值接口,还可以限制请求体大小,避免把“能解析”误认为“适合无限读取”。

Go json.Decoder 处理 HTTP 请求体时 EOF、UnexpectedEOF 和语法错误的静态分支图
图2:HTTP 请求体读取后,EOF 先结合接口契约判断,截断和语法错误再进入坏请求分支。

相关问题

空字符串调用 Decode 为什么不是 SyntaxError?

因为输入中没有开始解析的 JSON 值,解码器只能报告流结束。是否允许空字符串,是调用方的业务约束。

UnexpectedEOF 和 EOF 最容易怎么混淆?

完整值之后再读是 EOF;已经看到部分 JSON、但值尚未闭合就结束,是 UnexpectedEOF。

读取单个对象需要循环吗?

通常不需要。一次 Decode 成功后做字段校验即可;只有处理连续值或需要检查尾部多余内容时才继续读取。

判断 json.Decoder 的 EOF 时,先看读取目标,再看错误类别,最后套上接口对空输入的明确约定,排障日志和 HTTP 响应就不会把三种完全不同的情况混在一起。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>