当前位置: 首页 > news >正文

json.Unmarshal精度丢失问题分析

背景

上游服务将原始数据通过JSON字符串发送至下游服务,其中Data字段被定义为interface{}类型

下游服务使用encoding/json包下的Unmarshal方法将接收到的JSON反序列化到结构体hkEvent中。然而,在反序列化过程中,偶现精度丢失问题,例如id值从原始的10133102110768521变为10133102110768520。通过查询日志和分析链路结果发现,这一问题并非100%发生,仅在部分情况下出现

问题分析

原始数据中的id是一个17位大整数10133102110768521,其数值未超出int64的最大值(2^63-1 = 9223372036854775807),也未因其他情况引发报错。因此,问题很可能发生在对interface{}类型数据操作时。往下查询发现,JSON的number类型默认被解析为float64类型,而float64的精度仅约为15位十进制数字。由于原始id有17位,超出float64的精确表示范围,可能会导致精度丢失

验证

原始 id: 10133102110768521,这个数字有17位,而 float64 的精度是15-16位有效数字,因此用float64表示此数字时可能发生舍入错误。测试一下:

package main
import ("fmt"
)
func main() {var f float64 = 10133102110768521fmt.Printf("%.0f\n", f) // 输出:10133102110768520
}

运行结果显示输出为10133102110768520,而不是原始值10133102110768521,证实了精度丢失的原因

解决方案

方案1:使用具体结构体代替 interface{}

将interface{}替换为int64或string等具体类型。由于这是上游服务的结构体封装,无法直接修改,因此不采用此方案

方案2:下游服务结构体封装采用 json.Number类型

公共结构体,暂不进行这样的改动

方案3:使用 json.Decoder 并启用 UseNumber(采用的这种)

repo := redisRepo.NewTaskPoolRepo()
cmdText, err := repo.PopCommand(ctx, taskId)
if err != nil {if errors.Is(err, redis.Nil) {result.RuntimeStatus = StatusFinishreturn result}
}
taskItem := new(entity.TaskItem)
decoder := json.NewDecoder(strings.NewReader(cmdText))
decoder.UseNumber()
err = decoder.Decode(taskItem)
//err = json.Unmarshal([]byte(cmdText), taskItem)
if err != nil {result.RuntimeStatus = StatusNormalreturn result
}

方案4:使用自定义解码器

自定义 UnmarshalJSON 方法,实现 UnmarshalJSON 接口。完成特定字段的反序列化逻辑,如将大整数解析为 string 或 big.Int,demo示例:

import ("encoding/json""fmt"
)type Data struct {ID string `json:"id"`
}func (d *Data) UnmarshalJSON(data []byte) error {type Alias Dataaux := struct {ID interface{} `json:"id"`*Alias}{Alias: (*Alias)(d)}if err := json.Unmarshal(data, &aux); err != nil {return err}d.ID = fmt.Sprintf("%v", aux.ID)return nil
}func main() {jsonStr := `{"id": 101331033307280607}`var data Dataerr := json.Unmarshal([]byte(jsonStr), &data)if err != nil {panic(err)}fmt.Println("ID:", data.ID)
}

通过自定义 UnmarshalJSON,代码将 "id" 字段先解析为 interface{},然后强制转换为字符串,确保保留所有位数

优点:通过 interface{} 可以处理不同类型的JSON值,无需修改上游JSON格式,有一定的灵活性

缺点:显式地将 interface{} 转换为字符串有些繁琐,且有类型限制,如当JSON "id" 字段是其他类型(对象或数组),需要额外的类型检查和处理,也会带来一定的性能开销

方案5:其他包下的方法(推荐)

如 json-iterator/go包下的 UseNumber 选项,通过配置 UseNumber: true,强制将JSON中的所有数字解析为 jsoniter.Number 类型(一个字符串表示的数字),从而避免默认解析为 float64 导致的大整数精度丢失,代码示例:

package mainimport ("fmt"jsoniter "github.com/json-iterator/go"
)type hkEvent struct {ID   jsoniter.Number `json:"id"`Name string          `json:"name"`
}func main() {msg := []byte(`{"id": 101331033307280607, "name": "TestEvent"}`)json := jsoniter.Config{UseNumber: true}.Froze()var hkEvent hkEventerr := json.Unmarshal(msg, &hkEvent)if err != nil {panic(err)}id, _ := hkEvent.ID.Int64()fmt.Printf("ID: %d\n", id)fmt.Printf("Name: %s\n", hkEvent.Name)
}

http://www.lryc.cn/news/572689.html

相关文章:

  • vue3组件式开发示例
  • 大模型与搜索引擎的技术博弈及未来智能范式演进
  • MySQL查询语句的通配符*
  • 组态王工程运行时间显示
  • 【案例拆解】米客方德 SD NAND 在车联网中(有方模块)的应用:破解传统 TF 卡振动脱落与寿命短板
  • 在VTK中捕捉体绘制图像进阶(同步操作)
  • 零基础入门PCB设计 一实践项目篇 第三章(STM32开发板原理图设计)
  • 云计算处理器选哪款?性能与能效的平衡艺术
  • 【网络安全】文件上传型XSS攻击解析
  • 特征金字塔在Vision Transformer中的创新应用:原理、优势与实现分析
  • AS32系列MCU芯片I2C模块性能解析与调试
  • 408第二季 - 组成原理 - 流水线
  • Linux之线程同步与互斥
  • Rust 学习笔记:Unsafe Rust
  • 使用 .NET Core 8.0 和 SignalR 构建实时聊天服务
  • OPENPPP2 VMUX 技术探秘(高级指南)
  • 北京京东,看看难度
  • 解锁决策树:数据挖掘的智慧引擎
  • ffmpeg 给视频画圆圈
  • Electron (02)集成 SpringBoot:服务与桌面程序协同启动方案
  • 大白话说目标检测中的IOU(Intersection over Union)
  • Maven并行构建
  • 单点登录进阶:基于芋道(yudao)授权码模式的单点登录流程、代码实现与安全设计
  • SAP-ABAP:LOOP ... ASSIGNING高效处理内表数据详解
  • pandas polars 数据类型转换
  • 【pdf】Java代码生成PDF
  • lingma(阿里云Ai)结合idea使用
  • uni-app-配合iOS App项目开发apple watch app
  • Python按钮点击事件快速入门
  • vue3 reactive重新赋值