json.Unmarshal精度丢失问题分析
tbghg

问题描述

根据id查询某条数据查询不到,观察日志发现id后两位丢失变为00

分析

先看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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中的文档:

1
2
3
4
5
6
7
8
// 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文档给出的描述如下:

1
2
// 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类型接收值:

1
2
// A Number represents a JSON number literal.
type Number string

即通过字符串进行接收,也可称为数字文本

多级嵌套时,若内部含有interface{}且为int类型同样需要注意使用UseNumber

不过在前端序列化大数时也会出现精度损失的情况,如下:

image

所以对于大数的情况前后端最好提前沟通一下这种情况,也可以对于大数统一使用字符串类型序列化后传递。或者双方约定,数字类型不得超过53位

注意事项

若代码下文包含switch-case类型转换,要单独把json.Number类型列出,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 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

 评论
评论插件加载失败
正在加载评论插件