Go語言中,當uint64或int64數值超過錢包下載時,Tekon.app官網JSON序列化會因float64精度限制導致精度丟失。解決方案包括:使用json:"id,string"標籤轉為字符串、自定義類型實現Marshaler接口、或使用第三方庫json-iterator將大整數序列化為字符串。

關於token pocket下載解決JSON丟失問題_序列化

原理:

在 Go 語言中,當把 uint64 或 int64 類型的超大數值(如 uint64(1<<63) 以上,即超過 2^53)序列化 JSON 返回給前端時,會出現精度丟失 —— 核心原因是:JSON 標準中沒有整數類型,所有數字均以「雙精度浮點數(float64)」存儲,而 float64 僅能精確表示 [-2^53, 2^53] 範圍內的整數,超出該範圍的 64 位整數會被截斷或四捨五入。

一、問題本質:JSON 數字存儲機制

  1. float64 的精度限制

關於token pocket下載解決JSON丟失問題_json_02

  • float64 用 64 位存儲數據,其中:1 位符號位 + 11 位指數位 + 52 位尾數位;
  • 尾數位僅能表示 53 個有效二進制位(含整數部分和小數部分),因此超過 2^53 的整數無法被精確存儲
  • 臨界值:2^53 = 9007199254740992(約 9e15),超過該值的 64 位整數(如 9007199254740993)會被 float64 存儲為近似值(如 9007199254740992),導致精度丟失。
  1. Go 序列化的默認行為

關於token pocket下載解決JSON丟失問題_JSON_03

Go 標準庫 encoding/json 對 int64/uint64 類型的序列化規則:

  • 當數值 ≤ 2^53 時,序列化為 JSON 數字(float64 可精確表示);
  • 當數值 > 2^53 時,標準庫仍會序列化為 JSON 數字,但前端解析時會因 float64 精度限制丟失精度

關於token pocket下載解決JSON丟失問題_JSON_04


  • 示例:
package main

import (
  "encoding/json"
  "fmt"
)

func main() {
  type Data struct {
    ID uint64 `json:"id"`
  }

  // 超過 2^53 的超大數
  d := Data{ID: 9007199254740993} // 2^53 + 1
  jsonStr, _ := json.Marshal(d)
  fmt.Println("JSON 序列化結果:", string(jsonStr)) // 輸出:{"id":9007199254740992}(精度丟失!)
## ```

解決方式 1

package model

type Order struct {
  ID       uint64 `json:"id,string"` // 序列化時轉為字符串
  OrderNo  string `json:"order_no"`
  TotalAmt float64 `json:"total_amt"`
}

// 測試
func TestStringSerialize() {
  order := Order{
    ID:       9007199254740993, // 超大數
    OrderNo:  "ORDER_20240501",
    TotalAmt: 199.99,
  }

  jsonStr, _ := json.Marshal(order)
  fmt.Println(string(jsonStr)) 
  // 輸出:{"id":"9007199254740993","order_no":"ORDER_20240501","total_amt":199.99}(無精度丟失)
}

解決方式 2 自定義

package model

import (
  "encoding/json"
  "fmt"
  "math"
)

// 自定義 uint64 類型,實現 Marshaler 接口
type JSONUint64 uint64

// 實現 json.Marshaler 接口
func (u JSONUint64) MarshalJSON() ([]byte, error) {
  // 若數值超過 2^53,序列化為字符串;否則序列化為數字
  if u > JSONUint64(math.MaxInt64) || u > JSONUint64(math.Pow(2, 53)) {
    return json.Marshal(fmt.Sprintf("%d", u))
  }
  return json.Marshal(uint64(u))
}

// 訂單模型:使用自定義類型
type Order struct {
  ID       JSONUint64 `json:"id"` // 自定義類型
  OrderNo  string      `json:"order_no"`
  TotalAmt float64     `json:"total_amt"`
}

// 測試
func TestCustomSerialize() {
  // 超大數:序列化為字符串
  order1 := Order{ID: 9007199254740993, OrderNo: "ORDER_1", TotalAmt: 199.99}
  // 小數:序列化為數字
  order2 := Order{ID: 123456, OrderNo: "ORDER_2", TotalAmt: 99.99}

  json1, _ := json.Marshal(order1)
  json2, _ := json.Marshal(order2)
  fmt.Println(string(json1)) // {"id":"9007199254740993","order_no":"ORDER_1","total_amt":199.99}
  fmt.Println(string(json2)) // {"id":123456,"order_no":"ORDER_2","total_amt":99.99}
}

解決方式 3 引用第三放庫

go get github.com/json-iterator/go
package main

import (
  "fmt"
  jsoniter "github.com/json-iterator/go"
)

type Order struct {
  ID       uint64 `json:"id"`
  OrderNo  string `json:"order_no"`
  TotalAmt float64 `json:"total_amt"`
}

func main() {
  // 配置:將 uint64/int64 序列化為字符串(避免精度丟失)
  jsonCfg := jsoniter.Config{
    SerializeUint64AsNumber: false, // 關閉 uint64 作為數字序列化
    SerializeInt64AsNumber:  false, // 關閉 int64 作為數字序列化
  }.Froze()

  order := Order{
    ID:       9007199254740993,
    OrderNo:  "ORDER_20240501",
    TotalAmt: 199.99,
  }

  jsonStr, _ := jsonCfg.Marshal(order)
  fmt.Println(string(jsonStr)) 
  // 輸出:{"id":"9007199254740993","order_no":"ORDER_20240501","total_amt":199.99}
}