Go json.Decoder.Token 怎样按顺序读取 JSON 标记
来源:17golang原创
时间:2026-10-28 18:45:51 427浏览 收藏
当 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、输入截断和非法结构必须分别处理。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
101 收藏
-
443 收藏
-
260 收藏
-
Golang · Go教程 | 45分钟前 | Go教程 · database/sql · database/sql Go数据库 Rows.ColumnTypes ColumnType.Nullable253 收藏
-
343 收藏
-
145 收藏
-
308 收藏
-
334 收藏
-
343 收藏
-
177 收藏
-
133 收藏
-
147 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习