Go JSON 序列化大整数丢失精度分析

在 Go 项目中,我们经常使用 map[string]interface{} 来表示动态字段,或者用 JSON 序列化对象存入数据库、发送到 MQ。然而,当存储或传输 大整数(int64) 时,往往会出现精度丢失的问题。本文通过一个示例来详细分析原因,并给出解决方案。


1️⃣ 场景示例

假设我们有一个动态 Extra 字段,用于存储一些扩展信息,其中 oldTask 是一个 int64 大整数:

go 复制代码
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	// 模拟 Extra 字段
	extra := make(map[string]interface{})
	extra["oldTask"] = int64(7587721483007868979)

	fmt.Println("【1】原始写入 int64:")
	fmt.Printf("type=%T, value=%v\n\n", extra["oldTask"], extra["oldTask"])
}

输出:

go 复制代码
【1】原始写入 int64:
type=int64, value=7587721483007868979

✅ 在 Go 内存中,extra["oldTask"] 正确保存了 int64 值。


2️⃣ JSON 序列化

接下来我们将这个 map 序列化为 JSON 字符串(就像你在 BuildRiskPointsStrategy 中做的):

css 复制代码
bytes, _ := json.Marshal(extra)
fmt.Println("【2】json.Marshal 后:")
fmt.Println(string(bytes), "\n")

输出:

json 复制代码
【2】json.Marshal 后:
{"oldTask":7587721483007868979}

✅ Go 的 json.Marshal 对 int64 的处理是安全的,大整数不会丢失精度。


3️⃣ JSON 反序列化到 mapstringinterface{}

然而问题出现了,当我们反序列化 JSON 到 map[string]interface{} 时:

go 复制代码
var extra2 map[string]interface{}
_ = json.Unmarshal(bytes, &extra2)

fmt.Println("【3】json.Unmarshal 后:")
fmt.Printf("type=%T, value=%v\n\n", extra2["oldTask"], extra2["oldTask"])

输出:

go 复制代码
【3】json.Unmarshal 后:
type=float64, value=7587721483007869000

❌ 注意:

  • 反序列化后,extra2["oldTask"] 变成了 float64
  • 原始的 int64 值 7587721483007868979 精度丢失
  • 这是 Go 标准库 encoding/json 的默认行为:map[string]interface{} 中的数字全部解析成 float64。

4️⃣ 再转回 int64

如果我们强行把它转回 int64:

css 复制代码
lost := int64(extra2["oldTask"].(float64))
fmt.Println("【4】float64 → int64 后:")
fmt.Println("value =", lost)

输出:

go 复制代码
【4】float64 → int64 后:
value = 7587721483007869000
  • 可以看到,数字已经不再精确,丢失了最后几位。

5️⃣ 对比差值

go 复制代码
fmt.Println("【5】对比:")
fmt.Println("是否相等:", lost == int64(7587721483007868979))
fmt.Println("差值:", lost-int64(7587721483007868979))

输出:

arduino 复制代码
【5】对比:
是否相等: false
差值: 79
  • 原始值与反序列化后值不一致
  • 这就是"精度丢失"的根本原因

6️⃣ 问题分析

原因总结:

  1. Go 的 encoding/json 对 map[string]interface{} 反序列化时:

    • 所有数字默认是 float64
    • float64 最大安全整数范围是 -2^53 ~ 2^53
  2. 当 int64 值超出 2^53 时,float64 表示不精确 → 精度丢失

Go 官方文档说明:JSON numbers without decimal points are decoded as float64 when the target is interface{}。


7️⃣ 解决方案

方案 1:使用字符串存储大整数

less 复制代码
extra["oldTask"] = fmt.Sprintf("%d", oldTaskInt64)
  • JSON 序列化后:"oldTask":"7587721483007868979"
  • 写入数据库 / MQ / ES 时不会丢精度
  • 读取时再用 strconv.ParseInt 转回 int64

方案 2:使用 json.Number 解析

css 复制代码
var extra2 map[string]interface{}
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
decoder.UseNumber() // 使用 json.Number 代替 float64
decoder.Decode(&extra2)
  • extra2["oldTask"] 类型是 json.Number
  • 可以安全转成 int64 或 string
css 复制代码
num, _ := extra2["oldTask"].(json.Number).Int64()

方案 3:使用 struct 明确类型

go 复制代码
type RiskExtra struct {
	OldTask int64 `json:"oldTask"`
}
  • 避免 mapstringinterface{} → float64
  • JSON 反序列化会直接解析为 int64

✅ 结论

  1. Go mapstringinterface{} + JSON 是大整数丢失精度的高发场景
  2. 精度丢失的原因:JSON 默认解析数字为 float64
  3. 解决方案:
  • 使用字符串存储大整数
  • 使用 json.Number
  • 或者尽量使用 struct 明确类型
相关推荐
刚子编程8 小时前
我用 ASP.NET Core 做了个水稻病虫害检查系统
后端·asp.net
谢亮_vipxieliang8 小时前
用 PHP 构建轻量级 REST API:从路由到鉴权的完整实践
开发语言·后端·php
码事漫谈10 小时前
三步改掉 AI 味,附可直接复制的去 AI 味提示词
后端
IT_陈寒10 小时前
Redis卡顿的锅,这次真不是大key的错
前端·人工智能·后端
可乐鸡翅yeah_10 小时前
video.js 集成 hls.js 开发 M3U8 播放器,新手高频踩坑
开发语言·前端·javascript·后端·ecmascript·m3u8·音视频在线播放
程序猿_极客11 小时前
【免费】分享一套优质的基于SpringBoot的服装商城管理系统的设计与实现(带可视化图表、协同过滤功能),源码+文档+视频详解(讲解)
java·spring boot·后端·服装商城管理系统
明月_清风11 小时前
面对陌生的 GitHub 项目无从下手?这 4 个网站帮你快速读懂源码
前端·后端·github
bug菌12 小时前
🤔同事突然问我:Spring的注解 @Component 和 @Service 有何不同?
java·spring boot·后端
leobertlan12 小时前
痛苦系列 | DSP-02 频域切片:DTFT与DFT的探索
android·后端