Go json.Decoder.Token 怎样按顺序读取 JSON 标记
当 JSON 数据来自文件、网络连接或一串连续的顶层值时,直接把全部内容反序列化成一个大结构并不总是合适。json.Decoder.Token 会从输入流中按顺序取出下一个 JSON 标记:对象和数组的分隔符以 json.Delim 返回,布尔值、字符串、数字和 null 返回各自的基础类型,读完输入后返回 io.EOF。
官方文档:https://pkg.go.dev/encoding/json#Decoder.Token
把 Token 当成“JSON 词法游标”使用:需要知道结构边界时逐个读取,需要把某个完整值映射到 Go 类型时再交给 Decode。Token 不会替你把对象键和值组成业务结构。
Token 返回的不是一组统一结构
Token 的返回类型是接口,调用方应先处理错误,再根据动态类型判断当前标记。四个括号分隔符会以 json.Delim 出现;对象的键仍然是普通字符串,数字默认是 float64,除非通过其他方式保留数字文本。

package main
import (
"encoding/json"
"fmt"
"io"
"strings"
)
func main() {
dec := json.NewDecoder(strings.NewReader(`{"name":"Go","ok":true,"count":2}`))
for {
tok, err := dec.Token()
if err == io.EOF { // 正常读完当前 JSON 流,退出循环。
break
}
if err != nil { // 语法错误或输入读取错误必须保留,不能当成 EOF。
panic(err)
}
switch value := tok.(type) {
case json.Delim: // [、]、{、} 用于判断数组和对象边界。
fmt.Printf("分隔符: %q\\n", value)
default: // 键、字符串、布尔值、数字和 null 从这里进入。
fmt.Printf("值: %T %v\\n", value, value)
}
}
}
这段代码的关键是不要把“读完一个值”和“读完整个流”混为一谈。一个顶层对象读完后,如果输入后面还有另一个顶层 JSON 值,下一次调用仍可能得到新的标记。
用分隔符维护对象和数组的层级
Token 保证返回的 [ ] { } 是正确嵌套并匹配的,但它只负责给出标记,不会替你维护业务层级。需要统计当前深度、识别对象是否关闭或记录数组位置时,可以在读到左分隔符时入栈,在读到右分隔符时出栈。
func scanDepth(dec *json.Decoder) error {
depth := 0
for {
tok, err := dec.Token()
if err == io.EOF { // 流结束是正常结果,交给调用方决定是否接受。
return nil
}
if err != nil { // Token 已经发现非法 JSON 或底层读取失败。
return err
}
if delim, ok := tok.(json.Delim); ok {
switch delim {
case '{', '[':
depth++ // 左分隔符进入一层对象或数组。
case '}', ']':
depth-- // 右分隔符离开一层;匹配由 Decoder 保证。
}
fmt.Printf("depth=%d token=%q\\n", depth, delim)
}
}
}
如果还要知道对象键对应的值,不能只看字符串类型,因为键和值都可能是字符串。应在对象上下文中交替维护“期待键”与“期待值”的状态,或者在确定目标字段后把该字段的值交给 Decode。
数组遍历时让 Token 和 Decode 各做擅长的事
只扫描结构时可以一直调用 Token;但当数组元素需要进入结构体时,最清晰的写法是先用 Token 读出数组开始分隔符,再用 More 判断是否还有元素,最后用 Decode 读取完整元素。

type Item struct {
Name string `json:"name"`
ID int `json:"id"`
}
func readItems(dec *json.Decoder) ([]Item, error) {
first, err := dec.Token()
if err != nil { // 先确认输入确实以数组开始。
return nil, err
}
if first != json.Delim('[') { // 业务需要数组时,拒绝其他顶层值。
return nil, fmt.Errorf("want array, got %v", first)
}
var items []Item
for dec.More() { // More 只用于当前数组或对象内部,不用于任意流。
var item Item
if err := dec.Decode(&item); err != nil { // 完整元素交给结构体解码。
return nil, err
}
items = append(items, item)
}
end, err := dec.Token()
if err != nil { // 读取数组结束符时仍要检查输入错误。
return nil, err
}
if end != json.Delim(']') {
return nil, fmt.Errorf("want array end, got %v", end)
}
return items, nil
}
这个组合适合“外层结构需要检查,元素内容需要映射”的场景。它避免把整个输入一次性读进内存,也让数组边界和元素错误的位置更容易定位。
EOF、截断和非法分隔符要分开处理
在循环中,只有 errors.Is(err, io.EOF) 表示输入流自然结束。JSON 在字符串、数字或字面量中途被截断时,通常应作为错误返回;遇到不匹配的分隔符也会得到错误。不要用“任意错误都等于读完”来结束业务处理,否则坏数据会被当成成功。
func readStream(dec *json.Decoder) error {
for {
tok, err := dec.Token()
if err != nil {
if errors.Is(err, io.EOF) { // 只有自然结束才是成功退出。
return nil
}
return fmt.Errorf("read JSON token: %w", err) // 保留原始错误供上层判断。
}
fmt.Printf("token=%T %v\\n", tok, tok)
}
}
使用这段代码时别忘了导入 errors、fmt、io。如果输入可能由多个顶层 JSON 值组成,应把每一轮 Token 读取视为同一条流的连续消费,而不是假定只有一个对象。
常见问题
Token 能直接拿到对象字段名吗?能拿到字段名,但它只返回字符串,调用方仍要根据当前对象状态判断这个字符串是键还是值。
什么时候不该用 Token?如果目标是把完整 JSON 映射为稳定的结构体,并不需要检查中间层级,直接使用 Decode 通常更简单;Token 更适合流式扫描、结构探测和局部读取。
小结:先用 Token 看清 JSON 的顺序和边界,再用 More 管数组或对象内部循环,最后把完整业务值交给 Decode。正常 EOF、输入截断和非法结构必须分别处理。
Go net.IP.IsPrivate 怎样判断地址是否属于私有网络
- 上一篇
- Go net.IP.IsPrivate 怎样判断地址是否属于私有网络
- 下一篇
- Go xml.Decoder.Strict 怎样兼容不规范 XML 输入
-
- Golang · Go教程 | 28分钟前 | 标准库 · 错误处理 · go · 模板 · Go 模板渲染 text/template missingkey template.Option
- Go template.Option missingkey 怎样让模板缺字段直接报错
- 101浏览 收藏
-
- Golang · Go教程 | 28分钟前 |
- Go regexp.Expand 怎样按模板展开捕获组
- 443浏览 收藏
-
- Golang · Go教程 | 31分钟前 |
- Go math.Nextafter32 怎样取得指定方向的相邻浮点数
- 260浏览 收藏
-
- Golang · Go教程 | 45分钟前 | Go教程 · database/sql · database/sql Go数据库 Rows.ColumnTypes ColumnType.Nullable
- Go database/sql.ColumnType.Nullable 怎样判断列是否允许 NULL
- 253浏览 收藏
-
- Golang · Go教程 | 50分钟前 |
- Go encoding/base64.WithPadding 怎样设置自定义填充字符
- 343浏览 收藏
-
- Golang · Go教程 | 54分钟前 | go ·
- Go gzip.Reader.Reset 怎样复用解压读取器
- 145浏览 收藏
-
- Golang · Go教程 | 57分钟前 |
- Go tar.Reader.Next 怎样逐个读取归档文件
- 308浏览 收藏
-
- Golang · Go教程 | 58分钟前 | Go xml encoding/xml xml.Decoder.Strict AutoClose Entity
- Go xml.Decoder.Strict 怎样兼容不规范 XML 输入
- 334浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go net.IP.IsPrivate 怎样判断地址是否属于私有网络
- 343浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go strconv.UnquoteChar 怎样解析一个转义字符并保留尾串
- 177浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go json.Decoder.UseNumber 怎样避免 JSON 数字精度丢失
- 133浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go http.Flusher 怎样逐块刷新响应内容
- 147浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 760次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 757次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 841次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 807次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 637次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览

