json.Unmarshal精度丢失问题分析
问题描述
根据id查询某条数据查询不到,观察日志发现id后两位丢失变为00
分析
先看一段代码:
package main
import (
"bytes"
"encoding/json"
"fmt"
)
func main() {
var m map[string]interface{}
str := `{"id":9223372036854775807, "app":12312.3434}`
_ = json.Unmarshal([]byte(str), &m)
fmt.Printf("Unmarshal type:%T, value:%v\n", m["id"], m["id"])
fmt.Printf("Unmarshal type:%T, value:%v\n", m["app"], m["app"])
fmt.Println("-----------------------------------")
d := json.NewDecoder(bytes.NewReader([]byte(str)))
d.UseNumber()
_ = d.Decode(&m)
fmt.Printf("UseNumber type:%T, value:%v\n", m["id"], m["id"])
fmt.Printf("UseNumber type:%T, value:%v\n", m["app"], m["app"])
}
/*
Unmarshal type:float64, value:9.223372036854776e+18
Unmarshal type:float64, value:12312.3434
-----------------------------------
UseNumber type:json.Number, value:9223372036854775807
UseNumber type:json.Number, value:12312.3434
*/
使用json.Unmarshal
解析后,若未制定目标的类型,对于数字默认会采用float64
,见json.Unmarshal
中的文档:
// To unmarshal JSON into an interface value,
// Unmarshal stores one of these in the interface value:
// bool, for JSON booleans
// float64, for JSON numbers
// string, for JSON strings
// []interface{}, for JSON arrays
// map[string]interface{}, for JSON objects
// nil for JSON null
结论
因此,对于使用int64类型的数据,若数字超过了float64的范围,则会造成精度损失。
解决方案:使用UseNumber
,见示例代码中的第二部分。关于UseNumber
文档给出的描述如下:
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64.
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
使用UseNumber
后会使用json.Number
类型接收值:
// A Number represents a JSON number literal.
type Number string
即通过字符串进行接收,也可称为数字文本
多级嵌套时,若内部含有interface{}
且为int类型同样需要注意使用UseNumber
。
不过在前端序列化大数时也会出现精度损失的情况,如下:
所以对于大数的情况前后端最好提前沟通一下这种情况,也可以对于大数统一使用字符串类型序列化后传递。或者双方约定,数字类型不得超过53位
注意事项
若代码下文包含switch-case类型转换,要单独把json.Number
类型列出,例如:
// FormatInt64 将v转换成int64
func FormatInt64(v interface{}) (res int64, err error) {
switch vt := v.(type) {
case int64:
res = vt
return
case int:
res = int64(vt)
return
case int32:
res = int64(vt)
return
case int16:
res = int64(vt)
return
case int8:
res = int64(vt)
return
case float64:
res, err = strconv.ParseInt(strconv.FormatFloat(vt, 'f', 0, 64), 10, 64)
return
case float32:
res, err = strconv.ParseInt(strconv.FormatFloat(float64(vt), 'f', 0, 64), 10, 64)
return
case []byte:
res, err = strconv.ParseInt(string(vt), 10, 64)
return
case string:
res, err = strconv.ParseInt(vt, 10, 64)
return
default:
err = fmt.Errorf("not support %T", v)
}
return
}
因为不包含json.Number
会直接报错not support json.Number
评论