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

Go json.Decoder More 为什么不能单独判断顶层还有数据

来源:17golang原创

时间:2026-09-27 19:45:45 225浏览 收藏

很多 Go 代码把 dec.More() 当成“顶层输入后面还有没有 JSON”的判断器,结果在读取单个对象、连续 JSON 值和尾随垃圾时得到不同结论。关键点是:More 只回答当前正在解析的数组或对象是否还有成员;它不负责判断顶层流是否结束。

官方文档:https://pkg.go.dev/encoding/json

要点速览
  • 进入数组或对象后,使用 Token 读取左括号,再用 More 控制容器内循环。
  • 读取连续的顶层 JSON 值,使用 Decode 循环,并把 io.EOF 当作正常结束。
  • 只允许一个顶层值时,首个 Decode 后必须再次读取,不能用一次 More 代替尾随数据检查。

More 只看当前数组或对象的边界

官方定义是“当前数组或对象中是否还有另一个元素”。因此它需要一个容器上下文:在数组里,它判断下一个位置是否已经到 ];在对象里,它判断是否已经到 }。如果解码器还停留在顶层,调用 More 没有“顶层是否还有第二个值”这个语义。

这也是为什么 More 适合配合 Token 使用,而不适合拿来探测整个输入流。图1把解码器、容器栈、当前成员和右括号放在同一张静态结构图中,重点看容器边界,而不是把它理解为文件结束标志。

Go json.Decoder More 只在数组或对象容器内判断成员边界的结构说明图
图1:More 的判断范围停留在当前数组或对象容器内,这是静态结构说明图,不是运行截图。

遍历数组和对象时,先消费 Delim 再调用 More

标准写法是先用 Token 取出左括号,确认它是数组或对象,然后在对应容器内循环。数组元素可以交给 Decode,对象则要先读键,再读值。循环结束后仍要消费右括号,让解码器回到外层。

package main

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

func readArray() error {
	// 这个示例只在数组容器内使用 More,不把它当作顶层 EOF 判断。
	dec := json.NewDecoder(strings.NewReader(`[1,2,3]`))
	tok, err := dec.Token()
	if err != nil {
		return err
	}
	if delim, ok := tok.(json.Delim); !ok || delim != '[' {
		return fmt.Errorf("期望数组起始符,得到 %v", tok)
	}

	for dec.More() {
		var n int
		// Decode 消费当前数组元素;错误必须中止当前容器。
		if err := dec.Decode(&n); err != nil {
			return err
		}
		fmt.Println(n)
	}

	_, err = dec.Token() // 消费 ],恢复到数组外层。
	return err
}

func main() {
	// 真实业务可在这里处理 readArray 的错误;示例省略输出校验。
	if err := readArray(); err != nil && err != io.EOF {
		panic(err)
	}
}

若输入不是数组,应该根据业务返回类型错误,而不是继续调用 More。同理,对象遍历也要在读到 { 后使用 More,并严格按“键、值”成对消费。

顶层连续 JSON 值要用 Decode 和 io.EOF

顶层流可能是 {...}{...} 或多行 JSON 值。这种场景没有一个顶层数组让 More 负责计数,正确的结束信号是下一次 Decode 返回 io.EOF。如果返回的是语法错误或类型错误,应保留错误,不能把它当成“没有更多数据”。

func readJSONStream(r io.Reader) error {
	dec := json.NewDecoder(r)
	for {
		var item map[string]any
		// 每次 Decode 消费一个顶层 JSON 值,EOF 才表示流自然结束。
		err := dec.Decode(&item)
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return fmt.Errorf("读取顶层 JSON 失败: %w", err)
		}
		fmt.Printf("收到对象: %v\n", item)
	}
}

解码器会自行缓冲,并且可能从底层 io.Reader 多读一些字节,所以不要用底层 Reader 的一次读取结果推断 JSON 是否结束。图2把“连续顶层值”和“单文档尾随数据检查”分成两个边界,帮助区分正常 EOF、第二个值和坏字节。

Go 顶层 JSON 流使用 Decode 和 io.EOF 判断结束的关系说明图
图2:顶层流由 Decode 与 io.EOF 管理,More 不承担顶层结束判断;这是静态关系说明图。

只允许一个 JSON 文档时要再读一次

接口接收单个 JSON 对象时,最容易出现的漏洞是只调用一次 Decode,让输入 {"ok":true}{"extra":1} 悄悄通过。首个值解析成功只说明前半段有效,不能说明后面没有第二个值。

单文档策略可以在第一次成功后再调用一次 Decode:返回 io.EOF 才接受;如果读到第二个值或返回语法错误,就拒绝输入。需要允许空白时无需额外处理,因为解码器会跳过 JSON 空白。

场景正确判断不要使用
数组/对象内部循环Token + More + Decode/Token把 More 当成全局 EOF
连续顶层 JSON 值Decode 循环,io.EOF 结束用一次 More 探测剩余输入
单个 JSON 文档首个 Decode 后再次 Decode,必须得到 EOFDecode 成功一次就直接放行

常见疑问

调用 More 返回 false 能说明输入已经结束吗?

不能。它最多说明当前数组或对象没有下一个成员;顶层是否还有值,应继续使用 Decode 或 Token 读取。

为什么不直接用 Token 判断所有结束情况?

Token 适合精确观察括号和基础值,Decode 更适合把完整值交给结构体或映射。选择哪一个取决于你要遍历结构,还是要读取顶层值流。

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