应使用Decoder.Token()而非Decode()当JSON结构动态未知、需跳过无关字段或处理超大文件以避免OOM;它返回原始token不构造Go值,适合手动状态机解析,但需自行处理语法结构和类型判断。

什么时候该用 Decoder.Token 而不是 Decode
当你无法提前定义结构体(比如 JSON schema 动态)、需要跳过未知字段、或必须流式处理超大 JSON(避免全量内存反序列化)时,Decoder.Token 是唯一选择。它不构造 Go 值,只返回原始 token 类型和值,适合做轻量解析、字段筛选或协议桥接。
常见错误是试图用 Token() 读取完整对象后直接转成 map —— 它不帮你递归展开,你得自己写状态机来匹配 { / } 和 :。
-
Decode()适合已知结构、追求简洁;Token()适合未知结构、追求控制力 - 调用
Token()前必须确保 decoder 未被其他方法(如Decode)消费过缓冲区,否则行为未定义 - 它不会自动跳过空白或注释(JSON 标准不支持注释,但某些工具会加,这时会报
invalid character)
Token() 返回的 token 类型和典型用法
Token() 每次返回一个 json.Token 接口值,实际类型包括 json.Delim({、}、[、])、string(键名或字符串值)、float64(数字)、bool、nil。注意:数字总是 float64,即使 JSON 里是整数;字符串键和字符串值都返回 string,需靠上下文区分。
典型模式是用 switch t := tok.(type) 分支处理:
dec := json.NewDecoder(r)
for {
tok, err := dec.Token()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
switch t := tok.(type) {
case json.Delim:
if t == '{' {
// 进入对象,后续 expect string → : → value
} else if t == '}' {
// 退出当前对象
}
case string:
// 可能是 key(如果上一个是 { 或 ,),也可能是字符串值(如果上一个是 :)
case float64:
// 数字值,注意精度丢失风险(int64 > 2^53 会不精确)
}
}
如何安全地读取键值对而不崩溃
JSON 对象中,token 流是严格交替的:string(key)→ ':' → 任意值 → (',' 或 '}')。Token() 不校验语法,所以你必须自己维护“期待什么”的状态。最易踩的坑是:读到 string 后没判断下一个是不是 ':',直接当值用了。
- 用布尔变量记录是否刚读到 key(例如
expectValue := false),读到string且expectValue==false才认为是 key - 读到
':'后立即将expectValue = true;读到任意值 token 后重置为false - 遇到
','或'}'时,若expectValue == true,说明上一个值缺失(语法错误) -
Decoder.Peek()不存在,不能预读 —— 必须靠状态机 + 显式Token()调用推进
性能与边界注意事项
Token() 比 Decode() 慢约 2–3 倍(纯解析开销),但它内存占用恒定,适合 GB 级 JSON 流。真正影响性能的是频繁类型断言和字符串拷贝 —— Token() 返回的 string 是新分配的,别在循环里反复 strings.Equal 比较固定 key,应提前 var keyUser = []byte("user") 用 bytes.Equal([]byte(t), keyUser)。
容易被忽略的一点:Token() 不处理 UTF-8 BOM。如果输入带 BOM(如 \ufeff{...}),首次 Token() 会直接报错 invalid character ''。务必在传给 json.NewDecoder 前用 bytes.TrimPrefix 清除。

















