問題描述
今天遇到一個 json.Unmarshal() 反序列化位元組流到 interface{} 對象,int/int64 類型出現精度丢失的問題,記錄一下。下面是網上其他同學的類似的代碼,跟我的場景很像,是以直接拿過來作為案發現場代碼用了。
jsonStr := `{"id":3861708980690657283}`
result := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonStr), &result)
if err != nil {
fmt.Println(err)
}
id := result["id"]
fmt.Printf("type=%T, val=%v\n",id, id)
輸出
type=float64, val=3.861708980690657e+17
反序列化得到的 id 字段變成了 float64 類型,值輸出形式也變成了科學計數法形式,這當然難不倒我,略一百度,就查到了 json.Unmarshal() 反序列化位元組流到 interface{} 對象時,如果原來是 int 類型,會被反序列化成 float64 類型,網上的解決方案是對 int 字段進行類型強轉
id := int64(result["id"].(float64)) // 先強轉成 float64類型,再強轉成int64 類型
fmt.Printf("type=%T, val=%v\n",id, id)
輸出
type=int, val=3861708980690657280
檢視輸出結果,可以發現,類型變成了 int 類型,val 表現形式也變成了整數形式,但是出現了精度丢失,原來值是 3861708980690657283,變成了 3861708980690657280,最後一位的精度丢失了。
再次開始網上沖浪,通過參考中提到的 2 篇部落格發現了有 2 種解決方案:
解決方案
1、使用 decode+UseNumber()
不使用 json.Unmarshal() 來反序列對象,而是采用 decode+UseNumber() 來實作反序列化。
jsonStr := `{"id":3861708980690657283}`
result := make(map[string]interface{})
decoder := json.NewDecoder(bytes.NewBufferString(jsonStr))
decoder.UseNumber() // 指定使用 Number 類型
err := decoder.Decode(&result)
if err != nil {
fmt.Println(err)
}
id := result["id"]
fmt.Printf("type=%T, val=%v\n",id, id)
輸出
type=json.Number, val=3861708980690657283
輸出結果精度沒有丢失,類型是 json.Number。如果想把 id 轉換成 int4 類型,需要先轉換成字元串,再強轉成 int64 類型
aaa, err := strconv.ParseInt(fmt.Sprintf("%v", id), 10, 64) // 如果想把 id 轉換成 int4 類型,需要先轉換成字元串,再強轉成 int64 類型
fmt.Printf("type=%T, val=%v\n",aaa, aaa) // 輸出 type=int64, val=3861708980690657283
2、反序列化的對象改成不是 interface{}, 而是自定義結構體
可以發現上面先後遇到的這兩個問題,無論是科學計數法輸出,還是精度丢失,都是發生在 Unmarshal反序列化位元組流到 interface{} 對象
type Student struct {
ID int64 `json:"id"`
}
jsonStr := `{"id":3861708980690657283}`
var result Student
err := json.Unmarshal([]byte(jsonStr), &result)
if err != nil {
fmt.Println(err)
}
id := result.ID
fmt.Printf("type=%T, val=%v\n",id, id)
輸出
type=int64, val=3861708980690657283
by the way
網友建議前後端在使用 int64 類型進行互動時,盡量将 int64 轉成 string 來傳輸。