詳解Go語言中json轉(zhuǎn)換int64精度丟失的問題解決
在Go語言中,JSON轉(zhuǎn)換為`int64`類型時,如果JSON中的數(shù)字過大或過小,可能會導(dǎo)致精度丟失。這是因為JSON標(biāo)準(zhǔn)中并沒有定義整數(shù)類型,而是使用`double`(雙精度浮點(diǎn)數(shù))來表示所有數(shù)字,這在轉(zhuǎn)換為`int64`時可能會造成問題。
導(dǎo)致精度丟失的情況
以下是一些可能導(dǎo)致精度丟失的情況:
1. 大整數(shù):如果JSON中的數(shù)字超出了`int64`的范圍(-2^63 到 2^63-1),那么在轉(zhuǎn)換時可能會丟失精度。
2. 浮點(diǎn)數(shù):如果JSON中的數(shù)字是一個浮點(diǎn)數(shù),那么在轉(zhuǎn)換為`int64`時,可能會丟失小數(shù)點(diǎn)后的數(shù)值。
3. 負(fù)數(shù):對于負(fù)數(shù),如果它超出了`int64`的表示范圍,轉(zhuǎn)換時同樣會丟失精度。
解決方法
為了解決這個問題,你可以采取以下幾種方法:
1.使用`json.Number`類型:在解析JSON時,可以使用`json.Number`類型來保留原始的數(shù)值表示,然后再根據(jù)需要轉(zhuǎn)換為`int64`。
import (
"encoding/json"
"fmt"
"math/big"
)
func main() {
jsonStr := `{"number": "9223372036854775808"}` // 超出int64范圍的數(shù)字
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("轉(zhuǎn)換為big.Int: %s\n", num.String())
} else {
fmt.Printf("轉(zhuǎn)換為int64: %d\n", num)
}
}2.使用第三方庫:使用如`go.mongodb.org/mongo-driver/bson`這樣的庫,它們可能提供了更靈活的整數(shù)處理方式。
3.手動處理:在解析JSON之前,你可以手動檢查數(shù)字的大小,如果超出了`int64`的范圍,可以提前進(jìn)行處理。
4.明確JSON格式:在生成JSON時,確保數(shù)字在`int64`的范圍內(nèi),或者使用字符串來表示大整數(shù)。
請注意,處理大整數(shù)或浮點(diǎn)數(shù)時,可能需要使用`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:直接轉(zhuǎn)成字符串
jsonStr, _ := jsoniter.MarshalToString(order)
fmt.Println("jsonStr:", jsonStr)
// 使用2:直接轉(zhuǎn)成字節(jié)數(shù)組
jsonByteArr, _ := jsoniter.Marshal(order)
fmt.Println("jsonByteArr:", jsonByteArr)
// 使用3:反序列化之字符串轉(zhuǎn)結(jié)構(gòu)體
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:反序列化之字節(jié)數(shù)組轉(zhuǎn)結(jié)構(gòu)體
var order3 Order
var jsonNew = jsoniter.ConfigCompatibleWithStandardLibrary
// 自適應(yīng)類型
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:張三]}知識擴(kuò)展
處理 Go 語言 JSON 轉(zhuǎn)換時 int64 精度丟失的問題,本質(zhì)是 encoding/json 默認(rèn)將 JSON 數(shù)字解析為 float64 導(dǎo)致的。由于 float64 僅能精確表示 ±2^53 范圍內(nèi)的整數(shù),超出這個范圍的 64 位整數(shù)在轉(zhuǎn)換時會被舍入或截斷,從而引發(fā)精度丟失。
針對此問題,業(yè)界主要有三種不同的解決方案,它們各有優(yōu)劣,你可以根據(jù)自己的具體場景來權(quán)衡選擇。
方案一:使用 json.Number (最輕量、最精準(zhǔn))
這種方案通過延遲類型轉(zhuǎn)換來避免精度丟失。你可以將結(jié)構(gòu)體中可能包含大整數(shù)的字段聲明為 json.Number 類型(本質(zhì)是字符串),它就像一個“容器”,暫存原始的 JSON 數(shù)字文本。之后,你可以按需通過調(diào)用其 .Int64() 方法來獲得精確的 int64 值。
package main
import (
"encoding/json"
"fmt"
)
type MyData struct {
// 將可能超限的整數(shù)字段聲明為 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 轉(zhuǎn)換為 int64
id, err := data.LargeID.Int64()
if err != nil {
panic(err) // 處理轉(zhuǎn)換錯誤
}
fmt.Printf("轉(zhuǎn)換后的 int64 值: %d\n", id)
}關(guān)鍵點(diǎn): json.Number 字段需要手動調(diào)用 .Int64() 方法進(jìn)行轉(zhuǎn)換,這是獲取精確值的必要步驟,不可省略。
方案二:使用 json:",string" 標(biāo)簽 (最優(yōu)雅、最常用)
此方案利用結(jié)構(gòu)體標(biāo)簽,讓 encoding/json 在編解碼時自動將該字段在 JSON 字符串和 Go 數(shù)值之間進(jìn)行轉(zhuǎn)換。它在你無法改變前端(如 JavaScript)只接收字符串的情況下尤其有用。
package main
import (
"encoding/json"
"fmt"
)
type MyData struct {
// 使用 json:",string" 標(biāo)簽,讓 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 方法 (終極可控)
此方案通過實現(xiàn) json.Unmarshaler 接口來完全控制一個自定義類型的解析過程,獲得最高的靈活性。你可以將解析邏輯封裝成一個自定義類型,并在需要的地方復(fù)用。
package main
import (
"encoding/json"
"fmt"
"strconv"
)
// 定義一個新的類型,并為其實現(xiàn) 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 安全地轉(zhuǎn)換為 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("轉(zhuǎn)換后的 int64 值: %d\n", data.LargeID)
}方案對比與選擇建議
為了幫助你更直觀地選擇,我為你整理了以下對比表格:
| 對比維度 | 方案一:json.Number | 方案二:json:",string" 標(biāo)簽 | 方案三:自定義 UnmarshalJSON |
|---|---|---|---|
| 核心機(jī)制 | 使用 json.Number 類型暫存原始 JSON 數(shù)字字符串 | 利用標(biāo)簽 ,string 實現(xiàn) JSON 字符串與 Go 數(shù)值的自動轉(zhuǎn)換 | 為自定義類型實現(xiàn) json.Unmarshaler 接口,完全掌控解析過程 |
| 代碼侵入性 | 低:只需修改結(jié)構(gòu)體字段類型 | 低:只需在結(jié)構(gòu)體標(biāo)簽中添加 ,string | 中等:需要定義新類型并實現(xiàn)接口 |
| 對前端的影響 | 無:前端接收到的仍是 Number | 有:前端需處理 string 類型 | 可控:可根據(jù)實現(xiàn)邏輯決定 JSON 中的格式 |
| 性能開銷 | 中等:涉及額外類型轉(zhuǎn)換,有約 2-3 倍開銷 | 低:性能損耗極小 | 中等:類似 json.Number,取決于實現(xiàn) |
| 靈活性與擴(kuò)展性 | 低:功能相對單一 | 低:功能單一 | 極高:可根據(jù)需求定制任意邏輯 |
| 推薦場景 | 處理動態(tài)結(jié)構(gòu)或第三方 API 的 JSON 數(shù)據(jù)時 | 與JavaScript 前端交互時的首選方案 | 需要對特定字段進(jìn)行復(fù)雜校驗、轉(zhuǎn)換或類型限制時 |
總的來說:
- 如果你需要和前端(尤其是 JavaScript)交互,方案二 (
json:",string"標(biāo)簽) 是最直接、優(yōu)雅的選擇。 - 如果你正在處理一個結(jié)構(gòu)不固定的 JSON 數(shù)據(jù),方案一 (
json.Number) 能提供最大的靈活性。 - 如果以上方案都無法滿足你的特定需求,方案三 (自定義
UnmarshalJSON) 為你提供了終極的掌控力。
到此這篇關(guān)于詳解Go語言中json轉(zhuǎn)換int64精度丟失的問題解決的文章就介紹到這了,更多相關(guān)Go語言json轉(zhuǎn)int64精度丟失解決內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go?gRPC服務(wù)proto數(shù)據(jù)驗證進(jìn)階教程
這篇文章主要為大家介紹了Go?gRPC服務(wù)proto數(shù)據(jù)驗證進(jìn)階教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Go語言開發(fā)代碼自測絕佳go?fuzzing用法詳解
這篇文章主要為大家介紹了Go語言開發(fā)代碼自測絕佳go?fuzzing用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Golang?中的json.Marshal問題總結(jié)(推薦)
這篇文章主要介紹了Golang中的json.Marshal問題總結(jié),本文通過一個例子給大家詳細(xì)講解,本次提出的問題中,我們不難注意到其中的time.Time是一個匿名(Anonymous)字段,而這個就是答案的由來,需要的朋友可以參考下2022-06-06
Go語言CSP并發(fā)模型goroutine及channel底層實現(xiàn)原理
這篇文章主要為大家介紹了Go語言CSP并發(fā)模型goroutine?channel底層實現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05

