当前位置:首页 > 文章列表 > Golang > Go教程 > Go encoding/json Decoder UseNumber 保留大整数精度

Go encoding/json Decoder UseNumber 保留大整数精度

来源:17golang原创 2026-09-29 04:20:31 0浏览 收藏

当 JSON 被解码到 map[string]any 或 any 时,Go 的 encoding/json 默认把数字保存为 float64。如果字段是订单号、雪花 ID 或数据库主键,数值超过浮点安全整数范围后,精度可能在业务代码做类型断言之前就已经丢失。解决方法是使用 json.Decoder,在 Decode 前调用 UseNumber,让动态数字先保留为 json.Number。

UseNumber 不是把所有数字自动变成大整数,而是保留 JSON 数字的原始文本。后续仍要根据字段约束显式转换为 int64、uint64 或 big.Int,并处理转换错误。

为什么默认解码会改掉大整数

Go 官方文档明确说明:把 JSON 解码进接口值时,布尔值使用 bool,字符串使用 string,对象使用 map[string]any,而数字默认使用 float64。标准库源码中的数字转换逻辑也会在未启用 UseNumber 时调用 64 位浮点解析。

float64 只有有限的整数精度。像 9007199254740993 这样的值无法以 float64 精确表示,因此下面这种动态解码存在风险:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    input := []byte(`{"order_id":9007199254740993}`)

    var payload map[string]any
    // 直接 Unmarshal 到 any 时,JSON 数字默认会进入 float64。
    if err := json.Unmarshal(input, &payload); err != nil {
        panic(err)
    }

    // 此时再把 float64 转成整数,无法恢复已经丢掉的低位精度。
    fmt.Printf("%T %v\n", payload["order_id"], payload["order_id"])
}

问题不在 JSON 语法,而在“未知数字统一落到 float64”这一默认映射。只要目标是具体的整数结构体字段,解码器会按字段类型解析并检查范围;真正需要 UseNumber 的典型场景,是动态网关、通用事件、审计日志或无法预先确定字段模型的对象。

最小可用写法

最小改动是把 json.Unmarshal 换成 json.NewDecoder,并且在第一次 Decode 之前调用 UseNumber:

package main

import (
    "encoding/json"
    "fmt"
    "strings"
)

func main() {
    const input = `{"order_id":9007199254740993,"amount":12.50}`

    dec := json.NewDecoder(strings.NewReader(input))
    // 必须在 Decode 前启用,让接口值中的数字保存为 json.Number。
    dec.UseNumber()

    var payload map[string]any
    if err := dec.Decode(&payload); err != nil {
        panic(err)
    }

    orderID, ok := payload["order_id"].(json.Number)
    if !ok {
        panic("order_id 不是 JSON 数字")
    }

    // Int64 会检查语法和范围,不能忽略返回的错误。
    id, err := orderID.Int64()
    if err != nil {
        panic(fmt.Errorf("order_id 不是有效 int64: %w", err))
    }

    fmt.Println(id)
}

json.Number 本质上保存数字字面量。它提供 String、Int64 和 Float64 方法。是否调用哪一个,必须由字段语义决定,而不是看到 json.Number 就统一转成 float64。

JSON 数字字面量通过 Decoder UseNumber 保存为 json.Number 的静态结构说明图
图1:UseNumber 解码结构说明图;动态对象中的数字先保留为 json.Number,再由业务代码选择整数类型。

按字段范围选择 int64、uint64 或 big.Int

生产代码应先确定字段允许的范围,再选择转换方式。下面的表可以直接作为速查:

字段约束建议类型转换方式
有符号 64 位整数int64json.Number.Int64()
非负且允许到 uint64 上限uint64strconv.ParseUint(n.String(), 10, 64)
超出 64 位的纯整数big.IntSetString(n.String(), 10)
带小数或指数的数值按业务精度模型选择不要误用 big.Int

json.Number 没有 Uint64 方法,非负整数可以从它的字符串形式解析。对于超出 64 位的纯整数,则可以交给 math/big:

package numberutil

import (
    "encoding/json"
    "fmt"
    "math/big"
    "strings"
)

func ParseBigInteger(n json.Number) (*big.Int, error) {
    raw := n.String()

    // big.Int 只接受整数;小数点或指数必须由其他精度模型处理。
    if strings.ContainsAny(raw, ".eE") {
        return nil, fmt.Errorf("不是纯整数字面量: %q", raw)
    }

    value, ok := new(big.Int).SetString(raw, 10)
    if !ok {
        // SetString 用 ok 表示解析失败,调用方必须显式处理。
        return nil, fmt.Errorf("无法解析大整数: %q", raw)
    }

    return value, nil
}

如果业务协议允许小数,又要求十进制精度,应使用明确的十进制定点方案、金额最小单位整数或经过评估的十进制库。把金额先转成 float64 再格式化,仍然可能引入舍入问题。

结构体整数、json.Number 字符串和 big.Int 的静态类型边界说明图
图2:数字类型边界说明图;稳定字段优先使用结构体,动态数字保留文本,超范围整数再交给 big.Int。

字段模型稳定时优先定义结构体

UseNumber 主要服务于接口值中的动态数字。如果 API 字段稳定,直接定义结构体通常更简单:解码器会按 int64 或 uint64 解析,并在格式错误或超出范围时返回错误。

package order

import "encoding/json"

type Request struct {
    // 内部 ID 范围明确时,直接使用 uint64 获得范围检查。
    OrderID uint64 `json:"order_id"`

    // 跨语言链路可能无法安全承载大整数时,协议层可明确约定字符串。
    ExternalID string `json:"external_id"`
}

func DecodeRequest(data []byte) (Request, error) {
    var req Request
    // 目标字段是具体类型,不需要 UseNumber 参与动态数字映射。
    if err := json.Unmarshal(data, &req); err != nil {
        return Request{}, err
    }
    return req, nil
}

跨语言系统还要考虑发送方能力。浏览器 JavaScript、其他语言 SDK 或中间消息平台可能先把 JSON 数字读成双精度浮点数,再传给 Go;这时 Go 端使用 UseNumber 也无法恢复上游已经丢失的精度。对于必须跨多种运行时完整传输的超大 ID,把它定义为 JSON 字符串往往更稳妥。

错误处理和日志记录

数字精度问题容易演变成静默数据错误,因此应把转换失败当成输入错误,而不是用零值兜底。建议记录字段名、期望类型和错误类别,但不要原样记录包含隐私或敏感业务信息的整份请求。

  • 类型断言失败:字段可能是字符串、空值或嵌套对象,返回明确的字段类型错误。
  • Int64 失败:可能是小数、指数形式或超出有符号 64 位范围,不要忽略错误继续写库。
  • ParseUint 失败:检查负号、格式和范围,不能直接强制转换。
  • SetString 失败:确认字段确实是纯整数,不要把金额和科学计数法误当成大整数。

如果动态载荷还需要限制未知字段,注意 Decoder.DisallowUnknownFields 主要在目标为结构体时发挥作用;解码到 map[string]any 时,键本来就是动态集合,不能依赖它替代业务字段校验。

发布前检查清单

  1. 确认 UseNumber 在第一次 Decode 前调用。
  2. 确认大整数没有在中间层先进入 float64。
  3. 确认每个动态数字都按字段语义转换,并检查返回错误。
  4. 确认超出 64 位的值只在协议允许时进入 big.Int。
  5. 确认跨语言大 ID 是否需要改为字符串协议。
  6. 确认日志不输出完整敏感载荷,也不以零值掩盖解析失败。

常见问题

json.Unmarshal 能直接启用 UseNumber 吗?

不能。UseNumber 是 json.Decoder 的方法。需要这项行为时,应使用 json.NewDecoder,先调用 UseNumber,再调用 Decode。

调用 UseNumber 后所有字段都会变成 json.Number 吗?

不会。它影响的是解码到接口值中的 JSON 数字。布尔、字符串、数组和对象仍映射到各自类型;具体结构体中的整数、浮点字段也继续按字段类型解码。

json.Number.Int64 能处理 uint64 最大值吗?

不能。Int64 只接受有符号 64 位范围。非负且可能超过 int64 的字段应使用 strconv.ParseUint 解析字符串形式,并检查错误。

UseNumber 能解决上游 JavaScript 已经丢失的精度吗?

不能。它只能保留 Go 解码器收到的 JSON 数字字面量。如果发送方已经把大整数转成不精确的浮点值,原始低位已经不存在。跨语言超大 ID 最好在协议中定义为字符串。

总结起来,UseNumber 的价值是推迟数字类型决策:先保存原始字面量,再由业务边界决定是 int64、uint64、big.Int 还是其他精度模型。稳定字段优先用结构体,动态字段才使用 json.Number,并把每一次转换错误都显式处理。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
苔藓微距露珠手机壁纸提示词与自然纹理苔藓微距露珠手机壁纸提示词与自然纹理
上一篇
苔藓微距露珠手机壁纸提示词与自然纹理
MySQL 事务死锁日志对应索引与访问顺序的排查
下一篇
MySQL 事务死锁日志对应索引与访问顺序的排查
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    258次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    302次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    282次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    259次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    68次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码