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

Go JSONDecoder 连续读取多个对象时边界错乱怎么办

来源:17golang原创

时间:2026-10-18 08:05:45 184浏览 收藏

连续收到多个 JSON 对象时,json.Decoder 通常不会把第一个对象“读乱”。真正容易出错的是把三种边界混成一种:连续的顶层 JSON 值、一个数组里的多个元素,以及底层 io.Reader 被 Decoder 预读后的当前位置。迁移旧代码时,应先确认输入协议,再用连续 Decode 读取完整值;不要按字符串查找 }{,也不要把底层 Reader 的读取位置当成 JSON 边界。

处理 Go JSONDecoder 连续读取多个对象边界错乱,可先记住三点
  • 连续顶层对象可以循环调用 Decode,以 io.EOF 结束;数组输入则先处理 [ 和 ]。
  • NewDecoder 会建立自己的缓冲,可能提前从底层 Reader 读取后续字节。
  • InputOffset、SyntaxError 和 Buffered 解决的是不同定位问题,不能互相替代。

一、先确认输入到底是哪种 JSON 边界

旧代码常见的写法是读一行、拆一行,或者遇到 } 就认为对象结束。这在字符串、嵌套对象或没有换行的输入中都不可靠。Go 官方示例明确支持“多个独立 JSON 值组成的流”,例如两个对象之间只有空白字符,并不要求外层再包一个数组。

输入流、连续顶层对象、数组边界和 Decoder 缓冲的静态结构说明图
图1:JSON 输入协议与 Decoder 读取边界的静态结构说明图,不是运行截图。

迁移前先把输入分成三类:

  • 连续顶层值:{"id":1}{"id":2},每次 Decode 取一个完整值。
  • 数组:[{"id":1},{"id":2}],可以先用 Token 读取左括号,再配合 More 读取元素。
  • 非法拼接:对象后面跟着不属于下一个 JSON 值的字符,第二次 Decode 会报语法错误。

这三种输入不能用同一段“按行切割”逻辑处理。边界由 JSON 语法决定,换行只是空白,不是协议分隔符。

二、把一次性解析改成连续 Decode

如果协议就是连续的顶层 JSON 值,最小改法是保留同一个 Decoder,在循环中逐个解码。io.EOF 表示输入已经没有下一个值;其他错误才需要记录或返回。每轮重新声明目标变量,可以避免上一次对象的字段残留到下一次。

package stream

import (
	"encoding/json"
	"io"
)

type Event struct {
	ID   string `json:"id"`
	Kind string `json:"kind"`
}

func ReadEvents(r io.Reader) ([]Event, error) {
	dec := json.NewDecoder(r)
	var events []Event

	for {
		var event Event // 每轮使用新值,避免缺失字段继承上一轮内容。
		err := dec.Decode(&event)
		if err == io.EOF { // 输入正常结束,不把 EOF 当成失败。
			return events, nil
		}
		if err != nil {
			return nil, err // 语法错误或目标类型错误都保留原始错误。
		}
		events = append(events, event)
	}
}

这里的关键不是“多调用几次 Decode”,而是让 Decoder 持有同一条输入流。每次成功调用都会消费一个完整 JSON 值;空白会被跳过,下一次调用从下一个值开始。不要在循环里反复对同一个底层 Reader 创建新 Decoder,否则缓冲边界会变得不可控。

三、理解 Decoder 自己的预读缓冲

json.NewDecoder 会建立内部缓冲,而且可能从底层 Reader 读取超过当前请求的 JSON 值。于是,底层网络连接或文件看见的读取位置,可能已经越过第一个对象;这不等于 Decoder 把第二个对象交给了第一个目标变量。

旧代码如果需要在 JSON 流后继续读取另一种协议,不能直接继续使用原始 Reader。官方提供的 Buffered 可以取出 Decoder 尚未消费的内部字节,但它只在下一次 Decode 或 Token 前有效;调用方还要把这段缓冲与底层 Reader 的剩余内容拼接起来。

Decode、SyntaxError、TypeError、InputOffset 和 Buffered 的静态关系说明图
图2:Decoder 错误证据与偏移接口的静态关系说明图,不是运行截图。

因此迁移清单里应把“JSON 流后是否还有下一段协议”单独列出来。如果没有后续协议,直接让 Decoder 读到 EOF 最简单;如果有,就必须明确谁拥有缓冲和剩余字节。

四、用 InputOffset 和错误类型定位错位

看到“第二个对象解析失败”时,不要先猜是 Decoder 串数据。先区分错误来源:

现象证据迁移动作
输入正常结束err == io.EOF结束循环,不记录为业务失败
JSON 结构不完整或字符非法*json.SyntaxError结合错误偏移检查上一个值后的尾部
JSON 合法但目标类型不匹配*json.UnmarshalTypeError检查字段类型和协议版本,不要重切边界
想知道 Decoder 已经到哪dec.InputOffset()记录当前值结束后的输入字节偏移
func decodeWithOffset(r io.Reader) error {
	// 示例完整保留必要导入:实际文件还需 import fmt、encoding/json 和 io。
	dec := json.NewDecoder(r)
	for {
		var event Event
		err := dec.Decode(&event)
		if err == io.EOF {
			return nil
		}
		if err != nil {
			// 偏移用于定位输入位置,不代表底层 Reader 的读取位置。
			return fmt.Errorf("decode at byte %d: %w", dec.InputOffset(), err)
		}
	}
}

示例中的 fmt、encoding/json 和 io 应一起放进完整 import;这里省略包头只是为了突出定位逻辑。InputOffset 是 Decoder 当前 JSON 位置的证据,而不是“服务器收到多少字节”的网络指标。

五、按迁移清单修正旧读取代码

  1. 确认协议:连续顶层值用 Decode 循环,数组用 Token/More,不按换行或花括号切割。
  2. 固定 Decoder 生命周期:同一输入流只创建一个 Decoder,避免每轮重建。
  3. 区分结束和失败:只有 io.EOF 是正常结束,其他错误保留类型和上下文。
  4. 记录边界证据:必要时记录 InputOffset;需要切换协议时在下一次 Token 前处理 Buffered。
  5. 补回归样例:测试无空格拼接、换行拼接、嵌套对象、数组输入、类型错误和尾部非法字符。

最后检查目标变量的生命周期。复用结构体并不一定错,但必须明确缺失字段是否应该保留旧值;对事件流这类输入,按轮次创建新值通常更安全。只要输入协议、Decoder 生命周期和错误边界三者一致,连续读取多个 JSON 对象就不会靠猜测“哪里是边界”。

相关问题

多个 JSON 对象之间必须换行吗?

不必须。JSON 空白可以是空格、换行或制表符;连续顶层值只要各自是完整值,Decoder 就能逐个读取。

为什么 Decode 后底层 Reader 已经读过第二个对象?

因为 Decoder 允许预读并把未消费数据放在内部缓冲中。这不表示第二个值已经写入当前目标变量;需要切换协议时才使用 Buffered 处理剩余字节。

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