当前位置:首页 > 文章列表 > Golang > Go问答 > Go JSON 数字转 float64 为什么会丢失大整数

Go JSON 数字转 float64 为什么会丢失大整数

来源:17golang原创 2026-09-07 09:17:39 0浏览 收藏

如果接口里的订单号、流水号或雪花 ID 经过 json.Unmarshal 后变成了 float64,再转回整数时出现末尾数字变化,通常不是 JSON 文本自己改了,而是动态解码走了浮点数默认路径。Go 的 encoding/json 在把数字放进 any 时默认使用 float64;当整数超过双精度浮点数能精确表达的范围,精度就可能丢失。

需要保留大整数时,不要先把 JSON 解码到 any 再断言 float64。动态结构使用 Decoder.UseNumber(),随后把 json.Number 按业务语义解析成 int64uint64 或其他精确类型;结构固定时则直接定义整数型字段。

先看一个会悄悄改变数字的例子

下面的输入仍是一段合法 JSON,但解码目标是 map[string]any。打印类型可以看到 id 已经不是整数,而是 float64

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    raw := []byte(`{"id":9007199254740993}`)
    var payload map[string]any
    if err := json.Unmarshal(raw, &payload); err != nil {
        panic(err)
    }

    // 动态 JSON 的数字默认落为 float64,这里已经离开整数语义。
    fmt.Printf("%T: %v\n", payload["id"], payload["id"])
    // 重新编码只能编码当前的浮点值,不能找回被舍入的原文。
    encoded, err := json.Marshal(payload)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(encoded))
}

关键边界是 253。双精度浮点数并非不能存储更大的数量,而是不能保证每一个相邻整数都有独立表示。小于这个边界的常见计数值往往看不出差异,超过后就可能在转换、比较、签名或重新序列化时暴露问题。

JSON 数字进入 float64 后在 2 的 53 次方附近出现精度边界的原创技术框图
图1:动态 JSON 数字从 any 进入 float64 后的精度边界

UseNumber 解决的是“先别丢文本”

UseNumber 不会替你决定这个字段是有符号整数、无符号整数还是小数,它做的是把动态 JSON 数字保留为 json.Number。这个类型底层仍保存数字文本,所以可以延后到业务层做一次明确转换:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "strconv"
)

func readID(raw []byte) (uint64, error) {
    decoder := json.NewDecoder(bytes.NewReader(raw))
    // 保留数字原文,避免先经过 float64 的舍入。
    decoder.UseNumber()

    var payload map[string]any
    if err := decoder.Decode(&payload); err != nil {
        return 0, err
    }
    number, ok := payload["id"].(json.Number)
    if !ok {
        return 0, fmt.Errorf("id 不是 JSON 数字")
    }
    // 明确这是非负 ID,并让位数溢出成为可处理的错误。
    id, err := strconv.ParseUint(number.String(), 10, 64)
    if err != nil {
        return 0, fmt.Errorf("id 超出 uint64 或格式无效: %w", err)
    }
    return id, nil
}

这里的边界检查很重要:如果业务允许负数,应改用 number.Int64();如果字段可能含小数,就不能把它强行解析成 ID,而应使用 number.Float64() 并接受浮点语义,或交给定点/高精度数值库处理。

UseNumber 保留 json.Number 后按 int64 或 uint64 业务语义分流的原创技术框图
图2:UseNumber 保留数字文本,再按字段语义选择精确转换

固定结构优先直接声明整数类型

如果 JSON 字段是稳定的,最简单的方案不是维护一个动态 map,而是让结构体表达协议:

type Event struct {
    // 发送方保证这是非负的 64 位事件编号。
    EventID uint64 `json:"event_id"`
}

func decodeEvent(raw []byte) (Event, error) {
    var event Event
    // 目标类型明确,解码器不会先把 event_id 放进 float64。
    if err := json.Unmarshal(raw, &event); err != nil {
        return Event{}, err
    }
    return event, nil
}

结构体解码还会把超出目标类型范围的值报告为错误。这样比“先接收、后猜类型”更容易发现上游协议不一致,也能让接口测试直接断言 uint64 的结果。

按数据边界选择方案

场景推荐方式需要注意
固定 API 响应结构体 + int64/uint64让溢出直接成为解码错误
字段动态、但必须保留整数Decoder.UseNumber转换时检查符号和位数
跨语言传输超大 IDJSON 字符串协议双方统一按字符串校验
金额或任意精度小数字符串或定点/高精度类型不要依赖 float64 做精确比较

把精度问题挡在接口边界

排查时可以按三点确认:第一,查看解码目标是否为 anymap[string]any;第二,打印动态字段的实际类型,而不是只打印格式化后的值;第三,为超过 253 的代表性数字增加“解码—再编码—比较”的测试。只要数字承担身份标识、游标、版本号或签名输入,就应避免未经约束的浮点转换。

一句话判断:固定协议用明确整数类型,动态协议用 UseNumber 保留选择权,跨语言且无法保证整数能力时把 ID 当字符串传输。这样修复的不是某一个特殊数字,而是数据边界上的类型决策。

相关问题

  • 为什么重新 Marshal 后数字才看起来变了? 因为舍入通常发生在解码到 float64 时,重新编码只是把当前浮点值写回 JSON。
  • json.Number 能保证任意大整数吗? 它先保留文本,但最终转换到 int64uint64 仍有范围限制;超过范围要使用字符串或高精度方案。
  • 把所有 JSON 数字都改成字符串可以吗? 只有协议双方都接受这种表示才可以,不能在单方面改动后期待旧客户端仍按数字处理。
版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL invisible index 怎么验证索引删除前的影响MySQL invisible index 怎么验证索引删除前的影响
上一篇
MySQL invisible index 怎么验证索引删除前的影响
Redis Function 和临时 Lua 脚本有什么部署区别
下一篇
Redis Function 和临时 Lua 脚本有什么部署区别
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    171次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    101次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    21次使用
  • LangGPT提示词框架:结构化Prompt设计方法与开源工具指南
    LangGPT
    LangGPT是一种受编程语言启发的结构化提示词设计工具,提供双层框架、模块化模板及变量功能,帮助用户高效编写高质量Prompt。该项目已在GitHub免费开源,适用于内容创作、编程辅助等多场景。
    32次使用
  • ClickPrompt:AI提示词生成与优化工具,支持Stable Diffusion、ChatGPT及代码辅助
    ClickPrompt
    ClickPrompt是一款专为AI提示词编写者设计的开源在线工具,支持Stable Diffusion绘图、ChatGPT对话及GitHub Copilot代码辅助。提供Prompt自动生成、一键运行、社区分享及可视化优化功能,帮助用户高效获取精准AI输出。
    71次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码