详解Go语言中json转换int64精度丢失的问题解决
作者:leijmdas
在Go语言中,JSON转换为`int64`类型时,如果JSON中的数字过大或过小,可能会导致精度丢失。这是因为JSON标准中并没有定义整数类型,而是使用`double`(双精度浮点数)来表示所有数字,这在转换为`int64`时可能会造成问题。
导致精度丢失的情况
以下是一些可能导致精度丢失的情况:
1. 大整数:如果JSON中的数字超出了`int64`的范围(-2^63 到 2^63-1),那么在转换时可能会丢失精度。
2. 浮点数:如果JSON中的数字是一个浮点数,那么在转换为`int64`时,可能会丢失小数点后的数值。
3. 负数:对于负数,如果它超出了`int64`的表示范围,转换时同样会丢失精度。
解决方法
为了解决这个问题,你可以采取以下几种方法:
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`的范围内,或者使用字符串来表示大整数。
请注意,处理大整数或浮点数时,可能需要使用`math/big`包中的`Int`类型来避免精度丢失。
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 范围内的整数,超出这个范围的 64 位整数在转换时会被舍入或截断,从而引发精度丢失。
针对此问题,业界主要有三种不同的解决方案,它们各有优劣,你可以根据自己的具体场景来权衡选择。
方案一:使用 json.Number (最轻量、最精准)
这种方案通过延迟类型转换来避免精度丢失。你可以将结构体中可能包含大整数的字段声明为 json.Number 类型(本质是字符串),它就像一个“容器”,暂存原始的 JSON 数字文本。之后,你可以按需通过调用其 .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 数值之间进行转换。它在你无法改变前端(如 JavaScript)只接收字符串的情况下尤其有用。
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 数据时 | 与JavaScript 前端交互时的首选方案 | 需要对特定字段进行复杂校验、转换或类型限制时 |
总的来说:
- 如果你需要和前端(尤其是 JavaScript)交互,方案二 (
json:",string"标签) 是最直接、优雅的选择。 - 如果你正在处理一个结构不固定的 JSON 数据,方案一 (
json.Number) 能提供最大的灵活性。 - 如果以上方案都无法满足你的特定需求,方案三 (自定义
UnmarshalJSON) 为你提供了终极的掌控力。
到此这篇关于详解Go语言中json转换int64精度丢失的问题解决的文章就介绍到这了,更多相关Go语言json转int64精度丢失解决内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
