decoder.inputoffset 返回的是 json.decoder 内部读取器当前已消费的字节数,即从输入流开头到下一个待解析字符的偏移量,是只增的字节计数器,适用于进度追踪而非精确错误定位。

Decoder.InputOffset 返回的是什么位置
InputOffset 返回的是 json.Decoder 内部读取器(io.Reader)当前已消费的字节数,即从输入流开头到**下一个待解析字符**的偏移量。它不表示“刚刚解析完的 token 结束位置”,也不等于 JSON 语法错误发生处——而是解析器「准备读下一个字符」时的位置。
这意味着:如果你在调用 Decode 或 Token 后立刻查 InputOffset,得到的是该次解析操作「之后」的读取位置,不是错误现场。
定位语法错误时 InputOffset 的实际用途有限
Go 标准库的 encoding/json 在遇到语法错误(如 invalid character '}' looking for beginning of object key)时,InputOffset 通常**不指向错误字符本身**,而可能偏移 1–2 字节。原因在于底层词法分析器会预读(peek)若干字节做判断,错误报告位置和 InputOffset 不严格同步。
常见误区是以为 InputOffset 能精确定位报错字符,但实测中:
- 对
{"name": "foo" "age": 25}(缺少逗号),错误提示位置常比InputOffset小 1 - 对
{"name": "foo"(缺失 }),InputOffset可能停在 EOF,无法反推缺失符号位置 - 对 UTF-8 多字节字符(如中文),
InputOffset是字节偏移,不是 rune 偏移,直接映射到源码行号需额外转换
真正可用的定位方式:结合 Scanner 和手动 Token 遍历
若需精确报错位置(例如写 JSON linter 或调试工具),应放弃依赖 InputOffset,改用 Decoder.Token() 逐个消费 token,并记录每次调用前的读取位置。标准库未暴露内部 offset 更新时机,但可通过包装 io.Reader 实现:
type offsetReader struct {
r io.Reader
offset int64
}
func (o *offsetReader) Read(p []byte) (n int, err error) {
n, err = o.r.Read(p)
o.offset += int64(n)
return
}
func (o *offsetReader) Offset() int64 { return o.offset }
然后在每次 dec.Token() 前记录 offsetReader.Offset(),就能知道每个 token 的起始字节位置。配合 bytes.IndexByte 统计换行符,可换算出行列号。
InputOffset 在流式解析中的合理使用场景
它真正可靠的地方,是监控「已处理多少原始数据」,比如:
- 日志中记录「已安全解析前 12489 字节」,用于断点续传
- 与
io.LimitReader配合,防止单次 Decode 耗尽超大 payload - 在长连接 JSON 流中,定期上报当前解析进度(非精确错误定位)
注意:InputOffset 在 Decoder 复用时不会重置,且对底层 reader 的 seek、rewind 行为无感知——如果 reader 被外部修改过位置,InputOffset 就失效了。
别把它当调试器的「当前行号」用;它更像一个只增的字节计数器,适合粗粒度进度追踪,而非语法诊断。











