商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 详解Go语言中json转换int64精度丢失的问题解决

详解Go语言中json转换int64精度丢失的问题解决

  发布于2026-07-17 阅读(0)

扫一扫,手机访问

Go语言里处理JSON转int64的时候,有个细节特别容易让人踩坑——精度丢失。这事儿说大不大,但一旦碰上,排查起来还挺头疼的。原因其实不复杂:JSON标准里根本没有专门定义整数类型,它用double(双精度浮点数)来兜底所有数字。而float64在表示超大整数时,天然就存在精度瓶颈。

详解Go语言中json转换int64精度丢失的问题解决

导致精度丢失的情况

通常来说,下面这几种情况最容易中招:

1. 大整数:如果JSON里的数字超出了int64的范围(-2^63 到 2^63-1),转换时精度说丢就丢。

2. 浮点数:如果数字本身带小数,强转成int64时,小数点后面的部分自然会被截掉。

3. 负数:和正数一样,一旦超过表示范围,同样逃不掉精度损失。

解决方法

那么,怎么避开这个坑呢?有几种常见的做法。

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的安全范围内,或者直接约定用字符串来表示大整数。

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以内的整数,一旦越界,精度就开始打折扣了。

业内针对这个问题,主要有三种比较成熟的方案。每个方案各有侧重,你可以根据实际场景来选。

方案一:使用 json.Number (最轻量、最精准)

这个方案的核心思路是“延迟转换”。把可能包含大整数的字段声明为json.Number类型,它本质上就是个字符串,只负责暂存原始数字文本。之后你有需要了,再手动调用.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数值之间的双向转换。尤其适合前端(比如Ja vaScript)只接收字符串的场景。

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 数据时Ja vaScript 前端交互时的首选方案需要对特定字段进行复杂校验、转换或类型限制

总结一下

  • 和前端(尤其是Ja vaScript)打交道,方案二 (json:",string" 标签) 最直接、最优雅。
  • 如果遇到结构不固定的JSON数据,方案一 (json.Number) 的灵活性是最好的。
  • 要是前两个都满足不了你的特殊需求,方案三 (自定义UnmarshalJSON) 能给你终极的掌控感。
本文转载于:https://www.jb51.net/jiaoben/3624739tu.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注