解决 Go 64 位大数 JSON 序列化丢失问题
原理:
在 Go 语言中,当把 uint64 或 int64 类型的超大数值(如 uint64(1<<63) 以上,即超过 2^53)序列化 JSON 返回给前端时,会出现精度丢失—— 核心原因是:JSON 标准中没有整数类型,所有数字均以「双精度浮点数(float64)」存储,而 float64 仅能精确表示 [-2^53, 2^53] 范围内的整数,超出该范围的 64 位整数会被截断或四舍五入。
一、问题本质:JSON 数字存储机制
1. float64 的精度限制
- float64 用 64 位存储数据,其中:1 位符号位 + 11 位指数位 + 52 位尾数位;
- 尾数位仅能表示 53 个有效二进制位(含整数部分和小数部分),因此超过 2^53 的整数无法被精确存储;
- 临界值:
2^53 = 9007199254740992(约 9e15),超过该值的 64 位整数(如9007199254740993)会被 float64 存储为近似值(如9007199254740992),导致精度丢失。
2. Go 序列化的默认行为
Go 标准库 encoding/json 对 int64/uint64 类型的序列化规则:
当数值 ≤
2^53时,序列化为 JSON 数字(float64 可精确表示);当数值 >
2^53时,标准库仍会序列化为 JSON 数字,但前端解析时会因 float64 精度限制丢失精度
示例:package main import ( "encoding/json" "fmt" ) func main() { type Data struct { ID uint64 `json:"id"` } // 超过 2^53 的超大数 d := Data{ID: 9007199254740993} // 2^53 + 1 jsonStr, _ := json.Marshal(d) fmt.Println("JSON 序列化结果:", string(jsonStr)) // 输出:{"id":9007199254740992}(精度丢失!) ## ```
解决方式1
package model
type Order struct {
ID uint64 `json:"id,string"` // 序列化时转为字符串
OrderNo string `json:"order_no"`
TotalAmt float64 `json:"total_amt"`
}
// 测试
func TestStringSerialize() {
order := Order{
ID: 9007199254740993, // 超大数
OrderNo: "ORDER_20240501",
TotalAmt: 199.99,
}
jsonStr, _ := json.Marshal(order)
fmt.Println(string(jsonStr))
// 输出:{"id":"9007199254740993","order_no":"ORDER_20240501","total_amt":199.99}(无精度丢失)
}
解决方式2 自定义
package model
import (
"encoding/json"
"fmt"
"math"
)
// 自定义 uint64 类型,实现 Marshaler 接口
type JSONUint64 uint64
// 实现 json.Marshaler 接口
func (u JSONUint64) MarshalJSON() ([]byte, error) {
// 若数值超过 2^53,序列化为字符串;否则序列化为数字
if u > JSONUint64(math.MaxInt64) || u > JSONUint64(math.Pow(2, 53)) {
return json.Marshal(fmt.Sprintf("%d", u))
}
return json.Marshal(uint64(u))
}
// 订单模型:使用自定义类型
type Order struct {
ID JSONUint64 `json:"id"` // 自定义类型
OrderNo string `json:"order_no"`
TotalAmt float64 `json:"total_amt"`
}
// 测试
func TestCustomSerialize() {
// 超大数:序列化为字符串
order1 := Order{ID: 9007199254740993, OrderNo: "ORDER_1", TotalAmt: 199.99}
// 小数:序列化为数字
order2 := Order{ID: 123456, OrderNo: "ORDER_2", TotalAmt: 99.99}
json1, _ := json.Marshal(order1)
json2, _ := json.Marshal(order2)
fmt.Println(string(json1)) // {"id":"9007199254740993","order_no":"ORDER_1","total_amt":199.99}
fmt.Println(string(json2)) // {"id":123456,"order_no":"ORDER_2","total_amt":99.99}
}
解决方式3 引用第三放库
go get github.com/json-iterator/go
package main
import (
"fmt"
jsoniter "github.com/json-iterator/go"
)
type Order struct {
ID uint64 `json:"id"`
OrderNo string `json:"order_no"`
TotalAmt float64 `json:"total_amt"`
}
func main() {
// 配置:将 uint64/int64 序列化为字符串(避免精度丢失)
jsonCfg := jsoniter.Config{
SerializeUint64AsNumber: false, // 关闭 uint64 作为数字序列化
SerializeInt64AsNumber: false, // 关闭 int64 作为数字序列化
}.Froze()
order := Order{
ID: 9007199254740993,
OrderNo: "ORDER_20240501",
TotalAmt: 199.99,
}
jsonStr, _ := jsonCfg.Marshal(order)
fmt.Println(string(jsonStr))
// 输出:{"id":"9007199254740993","order_no":"ORDER_20240501","total_amt":199.99}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu