Go JSON中序列化大整数精度丢失的问题分析
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">场景示例</a></li><li><a href="#_label1">JSON 序列化</a></li><li><a href="#_label2">JSON 反序列化到 mapinterface{}</a></li><li><a href="#_label3">再转回 int64</a></li><li><a href="#_label4">对比差值</a></li><li><a href="#_label5">问题分析</a></li><li><a href="#_label6">解决方案</a></li><ul class="second_class_ul"><li><a href="#_lab2_6_0">方案 1:使用字符串存储大整数</a></li><li><a href="#_lab2_6_1">方案 2:使用json.Number解析</a></li><li><a href="#_lab2_6_2">方案 3:使用 struct 明确类型</a></li></ul><li><a href="#_label7">结论</a></li><ul class="second_class_ul"></ul></ul></div><p>在 Go 项目中,我们经常使用 <code>mapinterface{}</code> 来表示动态字段,或者用 JSON 序列化对象存入数据库、发送到 MQ。然而,当存储或传输 <strong>大整数(int64)</strong> 时,往往会出现精度丢失的问题。本文通过一个示例来详细分析原因,并给出解决方案。</p><p class="maodian"><a name="_label0"></a></p><h2>场景示例</h2>
<p>假设我们有一个动态 <code>Extra</code> 字段,用于存储一些扩展信息,其中 <code>oldTask</code> 是一个 <code>int64</code> 大整数:</p>
<div class="jb51code"><pre class="brush:go;">package main
import (
"encoding/json"
"fmt"
)
func main() {
// 模拟 Extra 字段
extra := make(mapinterface{})
extra["oldTask"] = int64(7587721483007868979)
fmt.Println("【1】原始写入 int64:")
fmt.Printf("type=%T, value=%v\n\n", extra["oldTask"], extra["oldTask"])
}
</pre></div>
<p>输出:</p>
<blockquote><p>【1】原始写入 int64:<br />type=int64, value=7587721483007868979</p></blockquote>
<p>在 Go 内存中,<code>extra["oldTask"]</code> 正确保存了 int64 值。</p>
<p class="maodian"><a name="_label1"></a></p><h2>JSON 序列化</h2>
<p>接下来我们将这个 map 序列化为 JSON 字符串(就像你在 <code>BuildRiskPointsStrategy</code> 中做的):</p>
<div class="jb51code"><pre class="brush:go;">bytes, _ := json.Marshal(extra)
fmt.Println("【2】json.Marshal 后:")
fmt.Println(string(bytes), "\n")
</pre></div>
<p>输出:</p>
<blockquote><p>【2】json.Marshal 后:<br />{"oldTask":7587721483007868979}</p></blockquote>
<p>Go 的 <code>json.Marshal</code> 对 int64 的处理是安全的,大整数不会丢失精度。</p>
<p class="maodian"><a name="_label2"></a></p><h2>JSON 反序列化到 mapinterface{}</h2>
<p>然而问题出现了,当我们反序列化 JSON 到 <code>mapinterface{}</code> 时:</p>
<div class="jb51code"><pre class="brush:go;">var extra2 mapinterface{}
_ = json.Unmarshal(bytes, &extra2)
fmt.Println("【3】json.Unmarshal 后:")
fmt.Printf("type=%T, value=%v\n\n", extra2["oldTask"], extra2["oldTask"])
</pre></div>
<p>输出:</p>
<blockquote><p>【3】json.Unmarshal 后:<br />type=float64, value=7587721483007869000</p></blockquote>
<p>注意:</p>
<ul><li>反序列化后,<code>extra2["oldTask"]</code> 变成了 <strong>float64</strong></li><li>原始的 int64 值 <code>7587721483007868979</code> <strong>精度丢失</strong></li><li>这是 Go 标准库 <code>encoding/json</code> 的默认行为:<code>mapinterface{}</code> 中的数字全部解析成 <code>float64</code>。</li></ul>
<p class="maodian"><a name="_label3"></a></p><h2>再转回 int64</h2>
<p>如果我们强行把它转回 int64:</p>
<div class="jb51code"><pre class="brush:go;">lost := int64(extra2["oldTask"].(float64))
fmt.Println("【4】float64 → int64 后:")
fmt.Println("value =", lost)
</pre></div>
<p>输出:</p>
<blockquote><p>【4】float64 → int64 后:<br />value = 7587721483007869000</p></blockquote>
<p>可以看到,数字已经不再精确,丢失了最后几位。</p>
<p class="maodian"><a name="_label4"></a></p><h2>对比差值</h2>
<div class="jb51code"><pre class="brush:go;">fmt.Println("【5】对比:")
fmt.Println("是否相等:", lost == int64(7587721483007868979))
fmt.Println("差值:", lost-int64(7587721483007868979))
</pre></div>
<p>输出:</p>
<blockquote><p>【5】对比:<br />是否相等: false<br />差值: 79</p></blockquote>
<ul><li>原始值与反序列化后值不一致</li><li>这就是“精度丢失”的根本原因</li></ul>
<p class="maodian"><a name="_label5"></a></p><h2>问题分析</h2>
<p><strong>原因总结</strong>:</p>
<p>1.Go 的 <code>encoding/json</code> 对 <code>mapinterface{}</code> 反序列化时:</p>
<ul><li>所有数字默认是 <code>float64</code></li><li>float64 最大安全整数范围是 <code>-2^53 ~ 2^53</code></li></ul>
<p>2.当 int64 值超出 2^53 时,float64 表示不精确 → 精度丢失</p>
<p>Go 官方文档说明:JSON numbers without decimal points are decoded as float64 when the target is <code>interface{}</code>。</p>
<p class="maodian"><a name="_label6"></a></p><h2>解决方案</h2>
<p class="maodian"><a name="_lab2_6_0"></a></p><h3>方案 1:使用字符串存储大整数</h3>
<div class="jb51code"><pre class="brush:go;">extra["oldTask"] = fmt.Sprintf("%d", oldTaskInt64)
</pre></div>
<ul><li>JSON 序列化后:<code>"oldTask":"7587721483007868979"</code></li><li>写入数据库 / MQ / ES 时不会丢精度</li><li>读取时再用 <code>strconv.ParseInt</code> 转回 int64</li></ul>
<p class="maodian"><a name="_lab2_6_1"></a></p><h3>方案 2:使用json.Number解析</h3>
<div class="jb51code"><pre class="brush:go;">var extra2 mapinterface{}
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
decoder.UseNumber() // 使用 json.Number 代替 float64
decoder.Decode(&extra2)
</pre></div>
<ul><li><code>extra2["oldTask"]</code> 类型是 <code>json.Number</code></li><li>可以安全转成 <code>int64</code> 或 <code>string</code></li></ul>
<div class="jb51code"><pre class="brush:go;">num, _ := extra2["oldTask"].(json.Number).Int64()
</pre></div>
<p class="maodian"><a name="_lab2_6_2"></a></p><h3>方案 3:使用 struct 明确类型</h3>
<div class="jb51code"><pre class="brush:go;">type RiskExtra struct {
OldTask int64 `json:"oldTask"`
}
</pre></div>
<ul><li>避免 mapinterface{} → float64</li><li>JSON 反序列化会直接解析为 int64</li></ul>
<p class="maodian"><a name="_label7"></a></p><h2>结论</h2>
<p>Go mapinterface{} + JSON 是大整数丢失精度的高发场景</p>
<p>精度丢失的原因:JSON 默认解析数字为 float64</p>
<p>解决方案:</p>
<ul><li>使用字符串存储大整数</li><li>使用 <code>json.Number</code></li><li>或者尽量使用 struct 明确类型</li></ul>
頁:
[1]