当前位置:首页 > 文章列表 > Golang > Go教程 > Go JSON Decoder 处理连续对象流的边界设计

Go JSON Decoder 处理连续对象流的边界设计

来源:17golang原创 2026-10-15 07:51:44 0浏览 收藏

处理 JSON 文件或 HTTP 响应体时,最容易混淆的是“一个 JSON 值”和“连续出现的多个 JSON 值”。Go 的 json.Decoder 面向的是输入流:每次调用 Decode 读取下一个完整值,读到输入末尾时返回 io.EOF。因此,连续对象流不应该被强行拼成一个大结构体,而应把“下一个对象是否存在”交给解码循环判断。

这篇只讨论三种边界:单个对象、空白分隔的多个顶层对象,以及数组容器中的多个对象。核心做法是先选择与输入形态匹配的读取模型,再把 io.EOF 和真正的语法或类型错误分开处理。

先看清输入到底是哪一种

下面三种文本看起来都像“很多对象”,但边界语义不同:

  • 单对象:{"id":1},适合一次 Decode。
  • 连续对象流:{"id":1} {"id":2},两个顶层 JSON 值由空白分隔,适合循环调用 Decode。
  • 数组:[{"id":1},{"id":2}],对象属于同一个数组容器,可用 Token 和 More 感知容器边界。

json.NewDecoder 自己管理缓冲,而且可能从底层 io.Reader 预读超出当前 JSON 值的部分。调用方应复用同一个 Decoder,不要为了“读下一个对象”重新创建 Reader 或 Decoder。

Go JSON Decoder 从输入流到 Message 对象并以 io.EOF 表示结束的静态结构说明图
图1:输入流、Decoder、Message 与 io.EOF 的边界关系说明图,不是运行截图。

用 Decode 循环读取连续对象

空白分隔的顶层对象流没有数组的开闭括号,最小可靠模式就是“先 Decode,再判断错误”。只有 io.EOF 表示正常读完;其它错误都应该保留上下文并返回。

package main

import (
	"errors"
	"fmt"
	"io"
	"strings"

	"encoding/json"
)

type Message struct {
	ID   int    `json:"id"`
	Text string `json:"text"`
}

func readMessageStream(input io.Reader) ([]Message, error) {
	dec := json.NewDecoder(input)
	var messages []Message

	for {
		var msg Message
		// Decode 每次只消费一个顶层 JSON 值,Decoder 在循环中保持不变。
		err := dec.Decode(&msg)
		if errors.Is(err, io.EOF) {
			// EOF 代表没有下一个对象,是流式读取的正常结束。
			return messages, nil
		}
		if err != nil {
			// 语法错误或类型错误不能当作自然结束返回。
			return nil, fmt.Errorf("decode message: %w", err)
		}
		messages = append(messages, msg)
	}
}

func main() {
	input := strings.NewReader(`{"id":1,"text":"first"}
{"id":2,"text":"second"}`)
	messages, err := readMessageStream(input)
	if err != nil {
		panic(err)
	}
	fmt.Println(messages)
}

这个循环有两个重要决定。第一,空白、换行和多个对象之间的间隔由 Decoder 处理,业务代码不需要自己切字符串。第二,io.EOF 只在“已经读完最后一个完整对象,且没有下一个值”时结束;如果最后一个对象缺少右括号,返回的会是语法错误,而不是 EOF。

把 EOF、空输入和坏数据分开

边界处理最好在读取函数内部完成,让调用方只看到“对象列表或错误”。下面这个变化表可以避免把异常吞掉:

输入状态Decode 返回函数行为
一个或多个完整对象,随后结束最后一次为 io.EOF返回已读取对象和 nil
只有空白第一次即为 io.EOF返回空切片和 nil,由业务决定是否允许
对象缺字段但 JSON 语法完整通常为 nil,未提供字段保留零值需要额外业务校验必填字段
逗号、引号或括号损坏非 EOF 错误包装错误并终止读取
对象类型与字段类型不匹配非 EOF 错误按输入错误返回,不继续消费后续内容

“缺字段”和“解码失败”不是一回事。标准库会把缺少的结构体字段保留为零值;如果接口要求 id 必须大于零,应在 Decode 成功后单独校验,而不要把 EOF 当作业务判断。

func readOne(input io.Reader) (Message, error) {
	var msg Message
	dec := json.NewDecoder(input)
	// 单对象接口只关心第一个值,Decode 成功后再做业务字段检查。
	if err := dec.Decode(&msg); err != nil {
		return Message{}, fmt.Errorf("decode one message: %w", err)
	}
	if msg.ID 

数组输入要用容器边界

如果上游明确发送的是 JSON 数组,就不要把数组误判为空白分隔的对象流。数组有开括号和闭括号,可以先读取 [,再用 More 判断是否还有元素,最后消费 ]。

func readMessageArray(input io.Reader) ([]Message, error) {
	dec := json.NewDecoder(input)
	var messages []Message

	// Token 先确认顶层确实是数组,而不是一个普通对象。
	token, err := dec.Token()
	if err != nil {
		return nil, fmt.Errorf("read array start: %w", err)
	}
	if delim, ok := token.(json.Delim); !ok || delim != '[' {
		return nil, fmt.Errorf("expected JSON array, got %v", token)
	}

	for dec.More() {
		var msg Message
		// More 只表示数组中仍有元素,具体对象仍由 Decode 解析。
		if err := dec.Decode(&msg); err != nil {
			return nil, fmt.Errorf("decode array item: %w", err)
		}
		messages = append(messages, msg)
	}

	// 读取闭括号,把数组容器边界消费完整。
	if token, err = dec.Token(); err != nil {
		return nil, fmt.Errorf("read array end: %w", err)
	}
	if delim, ok := token.(json.Delim); !ok || delim != ']' {
		return nil, fmt.Errorf("expected JSON array end, got %v", token)
	}
	return messages, nil
}
单对象、连续顶层对象流和 JSON 数组选择 Decode、More、Token 的静态关系说明图
图2:单对象、连续对象流和数组容器对应的 Decoder 接口关系说明图,不是运行截图。

把读取策略封装成稳定边界

在 HTTP body、文件或消息消费者里,读取函数通常还需要限制单次输入的来源和业务上限。可以把 Reader 交给函数,把“解码策略”固定下来;如果需要限制字节数,应在更外层使用带上限的 Reader,并让错误继续向上传播。

func consumeStream(input io.Reader, maxItems int, handle func(Message) error) error {
	dec := json.NewDecoder(input)
	for count := 0; count 

这个封装把三种结果分开:没有下一个值是正常结束,格式或类型错误是输入失败,超过业务上限是策略失败。对于长连接或持续生产的输入,函数也可以把 maxItems 换成上下文取消和单条处理回调,但不要改变 EOF 的含义。

选择方式时记住这张清单

  • 输入是多个没有数组括号的顶层对象:复用一个 Decoder,循环 Decode。
  • 输入是一个数组:先用 Token 确认 [,用 More 读取元素,最后消费 ]。
  • io.EOF 只表示没有下一个完整值,不能吞掉其它错误。
  • 结构体缺字段属于业务校验问题,字段类型错误属于解码错误,需分别处理。
  • 连续处理时优先逐条消费,是否收集到切片取决于内存预算和业务需要。

Go 官方 encoding/json 文档将 Decoder 定义为从输入流读取 JSON 值;本文的边界设计也建立在这个契约上。需要进一步确认方法语义时,可查看 https://pkg.go.dev/encoding/json 和 https://go.dev/src/encoding/json/stream.go。

相关问题

空输入应该报错吗? 对连续对象流来说,第一次 Decode 返回 EOF 通常表示空流;是否允许空流是业务规则,不是 JSON 解码器的错误。

为什么不把多个对象先读成 []byte 再 Unmarshal? 那样需要先确定完整边界并占用额外内存;输入本身是流时,Decoder 更适合逐个读取。

数组能不能直接用 Decode 读成切片? 可以,在数组规模可控时一次 Decode 到切片更简单;需要逐条消费或控制内存时,再使用 Token、More 和 Decode 的组合。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go encoding/csv 流式读取大文件的内存控制方案Go encoding/csv 流式读取大文件的内存控制方案
上一篇
Go encoding/csv 流式读取大文件的内存控制方案
Go JSON 请求兼容未知字段的解码策略
下一篇
Go JSON 请求兼容未知字段的解码策略
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    478次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    535次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    555次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    506次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    328次使用