天天看點

json.Unmarshal() 反序列化位元組流到 interface{} 對象,字段 int/int64 類型出現精度丢失

問題描述

今天遇到一個 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 來傳輸。