登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go jsontext Token 怎样在读取 token 数值(输入边界)

来源:17golang原创

时间:2026-10-25 05:16:12 251浏览 收藏

在 Go 1.27 的 encoding/json/jsontext 中,Token 表示 JSON 词法层的一个 token。读取数字时,关键不是直接调用 Int() 或 Uint(),而是先确认 Kind() == jsontext.KindNumber,再根据业务需要选择整数或浮点读取方法。

官方文档:https://pkg.go.dev/encoding/json/jsontext

安全读取 token 数值可以记成四步:先判数字类型,再选 accessor;用 errors.Is 识别语法或范围错误;最后注意 ReadToken 返回值可能依赖 Decoder 缓冲区。Int、Uint 出错时仍可能返回一个合理值,不能只看数值不看 error。
  • KindNumber 只说明 token 是 JSON 数字,不保证它适合目标 Go 类型。
  • Int 和 Uint 可能发生向零截断,超范围时会返回饱和值并带错误。
  • 从 Decoder.ReadToken 得到的原始 token,在下一次 Decoder 调用后不应继续依赖;需要保存时先 Clone。

先用 Kind 确认 token 真的是数字

Token 不只表示数字,还可以表示 null、布尔值、字符串以及对象和数组的起止分隔符。数字 accessor 对错误的 token 类型会 panic,因此输入来自外部 JSON 时,第一层判断应放在读取方法之前。

下面的辅助函数只把 JSON 数字交给 Int,其它 token 直接返回带上下文的错误。注释中的每个判断都对应一个输入边界,业务层可以再把错误映射成自己的字段错误。

package main

import (
    "fmt"

    "encoding/json/jsontext"
)

func readInt(tok jsontext.Token) (int64, error) {
    // 先挡住字符串、布尔值和结构分隔符,避免 Int 对非数字 token 触发 panic。
    if tok.Kind() != jsontext.KindNumber {
        return 0, fmt.Errorf("expected JSON number, got %s", tok.Kind())
    }

    // Int 会解析带符号的整数语义,返回值是否可用必须和 error 一起判断。
    n, err := tok.Int()
    if err != nil {
        return 0, fmt.Errorf("read integer %q: %w", tok.String(), err)
    }
    return n, nil
}

这里的 KindNumber 是必要条件,但不是完整的业务校验。例如 token 文本可以是带小数或指数的 JSON 数字,是否允许这类输入要交给 Int 的错误结果和业务规则共同决定。

Go jsontext Token 从 KindNumber 分流到 Int Uint Float 的静态关系说明图
图1:Token 类型与数值 accessor 的静态关系说明图,重点查看数字 token 与不同读取方法的边界;这不是运行截图。

Int、Uint 和 Float 的选择不要混用

有符号计数、可能出现负值的偏移量适合 Int();业务字段明确不允许负数且范围对应 uint64 时才使用 Uint()。普通 JSON 数字需要小数或指数语义时使用 Float()。Float32() 只适合调用方已经知道这个 token 按 32 位精度生成的场景,不能为了节省一个变量宽度就默认使用。

func readCount(tok jsontext.Token) (uint64, error) {
    // 无符号计数不接受非数字 token,负数和不符合无符号整数语义的文本交给 Uint 报错。
    if tok.Kind() != jsontext.KindNumber {
        return 0, fmt.Errorf("count must be a JSON number, got %s", tok.Kind())
    }
    n, err := tok.Uint()
    if err != nil {
        return 0, fmt.Errorf("invalid count %q: %w", tok.String(), err)
    }
    return n, nil
}

func readRatio(tok jsontext.Token) (float64, error) {
    // 比例允许小数,因此不能用 Int;Float 按 64 位精度读取 JSON 数字。
    if tok.Kind() != jsontext.KindNumber {
        return 0, fmt.Errorf("ratio must be a JSON number, got %s", tok.Kind())
    }
    n, err := tok.Float()
    if err != nil {
        return 0, fmt.Errorf("invalid ratio %q: %w", tok.String(), err)
    }
    return n, nil
}

不要把字符串形式的 "12" 当成数字 token 来调用这些方法。Token.String() 对字符串返回解码后的字符串值,对数字返回原始 JSON 表示;它适合放进错误信息或日志,但不能代替 Kind() 做类型判断。

语法错误、范围错误和截断要分开处理

Int() 与 Uint() 会用与 strconv.ErrSyntax、strconv.ErrRange 匹配的错误说明问题。范围错误时仍可能返回一个合理值,超出目标整数表示范围时还会饱和到最近的可表示值,所以生产代码应先处理 error,再决定是否把结果交给业务层。

import (
    "errors"
    "fmt"
    "strconv"

    "encoding/json/jsontext"
)

func classifyInt(tok jsontext.Token) (int64, error) {
    // 先确认调用前提,数值方法本身不会替调用方检查 Kind。
    if tok.Kind() != jsontext.KindNumber {
        return 0, fmt.Errorf("not a number: %s", tok.Kind())
    }
    n, err := tok.Int()
    if err == nil {
        return n, nil
    }
    // ErrSyntax 表示输入形态不符合当前整数读取语义,例如负号/小数规则不匹配。
    if errors.Is(err, strconv.ErrSyntax) {
        return 0, fmt.Errorf("integer syntax is invalid for %q: %w", tok.String(), err)
    }
    // ErrRange 表示不能无损放进 int64;即使 n 有值,也不能当成精确结果。
    if errors.Is(err, strconv.ErrRange) {
        return 0, fmt.Errorf("integer is out of int64 range: %w", err)
    }
    return 0, fmt.Errorf("read integer: %w", err)
}

文档明确说明整数读取会忽略小数部分并向零截断,因此 12.9 这类输入不能在业务上默认视为“合法整数”。如果字段必须是整数,应在 token 层保留原始表示并增加格式约束,不能把截断后的结果悄悄写回数据库或计数器。

Go jsontext Token 数字读取的语法范围截断和 Decoder 缓冲区边界说明图
图2:数值 token 的语法、范围、截断与生命周期边界说明图;图中关系是静态解释,不是运行结果。

ReadToken 返回值需要注意 Clone

如果 token 是通过 Decoder.ReadToken 得到的,它的原始 JSON 文本可能由 Decoder 的内部缓冲区支持。下一次调用 Decoder 的方法后,这个 token 不应再被当成独立值使用。需要把 token 放进队列、缓存、异步任务或错误记录时,先调用 Clone()。

func keepNumber(dec *jsontext.Decoder) (jsontext.Token, error) {
    tok, err := dec.ReadToken()
    if err != nil {
        return jsontext.Token{}, err
    }
    if tok.Kind() != jsontext.KindNumber {
        return jsontext.Token{}, fmt.Errorf("expected number, got %s", tok.Kind())
    }

    // 跨过下一次 Decoder 调用仍要使用,因此复制脱离 Decoder 缓冲区的 token。
    return tok.Clone(), nil
}

相反,jsontext.Int(42)、jsontext.Uint(42)、jsontext.Float(0.5) 等构造出来的 token 本身已经独立保存,不需要为了生命周期再次 Clone。这一区别只影响 token 的存活时间,不会改变数字解析的类型或范围规则。

常见问题

调用 Token.Int 之前一定要判断 Kind 吗?

只要 token 来源不受你完全控制,就应该判断。Int、Uint 会对非数字 token panic,提前判断能把输入问题转成普通 error。

Uint 能读取负数吗?

不能把负数当作无符号整数接受。应让 Uint 返回错误,并按 strconv.ErrSyntax 或 strconv.ErrRange 分类处理。

为什么 Int 返回了数值还要看 error?

因为语法不匹配或超出 int64 范围时,文档允许返回一个合理值或饱和值。只有 error 为 nil 时,才适合把该数值当作无损读取结果。

Token.Clone 会把数字转换成别的类型吗?

不会。Clone 只把依赖 Decoder 缓冲区的原始 token 复制成独立 token,不负责类型转换、范围检查或业务校验。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>