发布于2026-07-17 阅读(0)
扫一扫,手机访问
Go语言里处理JSON转int64的时候,有个细节特别容易让人踩坑——精度丢失。这事儿说大不大,但一旦碰上,排查起来还挺头疼的。原因其实不复杂:JSON标准里根本没有专门定义整数类型,它用double(双精度浮点数)来兜底所有数字。而float64在表示超大整数时,天然就存在精度瓶颈。

通常来说,下面这几种情况最容易中招:
1. 大整数:如果JSON里的数字超出了int64的范围(-2^63 到 2^63-1),转换时精度说丢就丢。
2. 浮点数:如果数字本身带小数,强转成int64时,小数点后面的部分自然会被截掉。
3. 负数:和正数一样,一旦超过表示范围,同样逃不掉精度损失。
那么,怎么避开这个坑呢?有几种常见的做法。
1. 使用 json.Number 类型
解析JSON时,用json.Number这个类型来“暂存”原始的数字文本,之后再按需转成int64。这个方法的关键在于延迟了类型转换,从而保留了原始精度。
import (
"encoding/json"
"fmt"
"math/big"
)
func main() {
jsonStr := `{"number": "9223372036854775808"}` // 超出int64范围的数字
var data map[string]json.Number
err := json.Unmarshal([]byte(jsonStr), &data)
if err != nil {
panic(err)
}
number := data["number"]
num := new(big.Int)
num, ok := number.Int64()
if !ok {
num, _ = new(big.Int).SetString(string(number), 10)
fmt.Printf("转换为big.Int: %s\n", num.String())
} else {
fmt.Printf("转换为int64: %d\n", num)
}
}2. 使用第三方库
像go.mongodb.org/mongo-driver/bson这类库,内置了更灵活的整数处理机制,有时候能直接解决问题。
3. 手动预处理
在调用JSON解析之前,先手动检查数字大小。一旦发现超出int64范围,可以提前做特殊处理。
4. 约定JSON格式
如果JSON数据由你控制,那就从源头约定好:确保所有数字都在int64的安全范围内,或者直接约定用字符串来表示大整数。
jsoniter "github.com/json-iterator/go"
func Test0008_Jsoniter_Marshal(t *testing.T) {
order := Order{
Id: baseutils.SnowflakeNextVal(),
OrderId: "12345678",
Money: 99.99,
CreateTime: time.Now(), // 2023-12-05T16:19:33.943989108+08:00
Extend: map[string]string{"name": "张三"},
}
// 使用1:直接转成字符串
jsonStr, _ := jsoniter.MarshalToString(order)
fmt.Println("jsonStr:", jsonStr)
// 使用2:直接转成字节数组
jsonByteArr, _ := jsoniter.Marshal(order)
fmt.Println("jsonByteArr:", jsonByteArr)
// 使用3:反序列化之字符串转结构体
str := `{"id":666, "order_id":"12345678", "money": 99.99, "create_time":"2023-12-05T16:19:33.943989108+08:00", "extend":{"name":"张三"}}`
var order2 Order
err := jsoniter.UnmarshalFromString(str, &order2)
if err != nil {
fmt.Println("err2:", err)
}
fmt.Println("order2:", order2)
// 使用4:反序列化之字节数组转结构体
var order3 Order
var jsonNew = jsoniter.ConfigCompatibleWithStandardLibrary
// 自适应类型
extra.RegisterFuzzyDecoders()
err = jsonNew.Unmarshal(jsonByteArr, &order3)
if err != nil {
fmt.Println("err3:", err)
}
fmt.Println("order3:", order3)
}
order3: {572839995925069824 12345678 99.99 2024-04-29 17:41:37.8819085 +0800 CST map[name:张三]}说白了,Go里JSON转换int64精度丢失这个问题的根子,就在encoding/json默认把JSON数字解析成float64上。float64只能精确表达±2^53以内的整数,一旦越界,精度就开始打折扣了。
业内针对这个问题,主要有三种比较成熟的方案。每个方案各有侧重,你可以根据实际场景来选。
方案一:使用 json.Number (最轻量、最精准)
这个方案的核心思路是“延迟转换”。把可能包含大整数的字段声明为json.Number类型,它本质上就是个字符串,只负责暂存原始数字文本。之后你有需要了,再手动调用.Int64()方法拿到精确的int64值。
package main
import (
"encoding/json"
"fmt"
)
type MyData struct {
// 将可能超限的整数字段声明为 json.Number
LargeID json.Number `json:"id"`
}
func main() {
jsonStr := `{"id": 9223372036854775807}`
var data MyData
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
panic(err)
}
// 安全地将 json.Number 转换为 int64
id, err := data.LargeID.Int64()
if err != nil {
panic(err) // 处理转换错误
}
fmt.Printf("转换后的 int64 值: %d\n", id)
}关键点: json.Number字段必须手动调用.Int64()方法,这个步骤不能省,否则拿不到精确值。
方案二:使用 json:",string" 标签 (最优雅、最常用)
这个方案利用结构体标签,让encoding/json在编解码时自动完成JSON字符串和Go数值之间的双向转换。尤其适合前端(比如Ja vaScript)只接收字符串的场景。
package main
import (
"encoding/json"
"fmt"
)
type MyData struct {
// 使用 json:",string" 标签,让 ID 在 JSON 中以字符串形式传递
LargeID int64 `json:"id,string"`
}
func main() {
jsonStr := `{"id": "9223372036854775807"}`
var data MyData
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
panic(err)
}
fmt.Printf("Unmarshal 得到的 int64 值: %d\n", data.LargeID)
}方案三:自定义 UnmarshalJSON 方法 (终极可控)
这种方案通过实现json.Unmarshaler接口,让你完全掌控自定义类型的解析过程。你可以把解析逻辑封装成独立类型,然后在需要的地方复用。
package main
import (
"encoding/json"
"fmt"
"strconv"
)
// 定义一个新的类型,并为其实现 UnmarshalJSON 接口
type PreciseInt int64
func (pi *PreciseInt) UnmarshalJSON(b []byte) error {
// 1. 先解析到 json.Number
var n json.Number
if err := json.Unmarshal(b, &n); err != nil {
return err
}
// 2. 再从 json.Number 安全地转换为 int64
i, err := n.Int64()
if err != nil {
return err
}
*pi = PreciseInt(i)
return nil
}
type MyData struct {
LargeID PreciseInt `json:"id"`
}
func main() {
jsonStr := `{"id": 9223372036854775807}`
var data MyData
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
panic(err)
}
fmt.Printf("转换后的 int64 值: %d\n", data.LargeID)
}方案对比与选择建议
为了帮你做个更直观的判断,这里整理了张对比表:
| 对比维度 | 方案一:json.Number | 方案二:json:",string" 标签 | 方案三:自定义 UnmarshalJSON |
|---|---|---|---|
| 核心机制 | 使用 json.Number 类型暂存原始 JSON 数字字符串 | 利用标签 ,string 实现 JSON 字符串与 Go 数值的自动转换 | 为自定义类型实现 json.Unmarshaler 接口,完全掌控解析过程 |
| 代码侵入性 | 低:只需修改结构体字段类型 | 低:只需在结构体标签中添加 ,string | 中等:需要定义新类型并实现接口 |
| 对前端的影响 | 无:前端接收到的仍是 Number | 有:前端需处理 string 类型 | 可控:可根据实现逻辑决定 JSON 中的格式 |
| 性能开销 | 中等:涉及额外类型转换,有约 2-3 倍开销 | 低:性能损耗极小 | 中等:类似 json.Number,取决于实现 |
| 灵活性与扩展性 | 低:功能相对单一 | 低:功能单一 | 极高:可根据需求定制任意逻辑 |
| 推荐场景 | 处理动态结构或第三方 API 的 JSON 数据时 | 与Ja vaScript 前端交互时的首选方案 | 需要对特定字段进行复杂校验、转换或类型限制时 |
总结一下:
json:",string" 标签) 最直接、最优雅。json.Number) 的灵活性是最好的。UnmarshalJSON) 能给你终极的掌控感。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8