encoding/json包在开发的过程中,占据着相当重要的角色。
然而角色有多重,坑就有多大。下面记录的便是在踩json雷中的辛酸泪(>﹏<)
反序列化时的数值处理及float64精度问题
众所周知(其实是最近才知道),golang原生的encoding/json在反序列化的时候,默认情况下会把所有数值类型转成float64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import (
"encoding/json"
"fmt"
)
func test_std_json(){
var m []interface{}
if err := json.Unmarshal([]byte(`[100, null, 1.2, "1234"]`), &m); err == nil {
for _, v := range m {
fmt.Printf("type: %T, value: %v\n",v, v )
}
} else {
fmt.Println("Unmarshal error: ", err)
}
}输出
1 | type: float64, value: 100 |
分析
重点在于 json.Unmarshal 这个方法.它的类型转换是这么搞的:
To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:
1
2
3
4
5
6bool, 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以上解释摘自官方文档
一般情况下是不会有什么问题的,但是,如果我们传进去的是一个大整型(超过float64定义的范围),那么就粗大事了。举个栗子:
1 | func test_std_json_large(){ |
- 输出
1 | type: float64, value: 100 |
- 注意到了吗?上面的数字1234567890123456789是一个在int64范围内,但是在float64之外的数值。
- 反序列化之后,这个值变成了123456789012345678!!!试想一下,本来你手头有1234567890个亿,经过json.Unmarshal,就只剩123456789个亿了┭┮﹏┭┮
- 但是没关系,此事并非不可解。下面我们来看看两种解决方案。
方法一 :使用标准库的json.Decoder
- golang的标准json库提供了一种方案:将数值类型直接转成json.Number类型,让用户稍后自己根据需要转成数值。具体的实现是利用json库提供的Decoder:
1
2
3
4
5
6
7
8
9
10
11
12func Decoder(jsonStr string) {
var result map[string]interface{}
decoder := json.NewDecoder(bytes.NewReader([]byte(jsonStr)))
//seNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64.
decoder.UseNumber()
decoder.Decode(&result)
fmt.Printf("反序列化后:\t%#v\n", result)
pid := result["config"].(map[string]interface{})["pid"]
fmt.Printf("PID类型:\t%T \nPID值:\t%v\n", pid, pid)
pidValue, _ := pid.(json.Number).Int64()
fmt.Printf("Int64:%d", pidValue)
} - 输出
1 | 反序列化后: map[string]interface {}{"userID":"1", "config":map[string]interface {}{"target_type":"1", "pid":"1234567890123456789"}} |
- 这样,我们的1234567890个亿还是1234567890个亿。可以把json.Number当成字符串,标准库对于这个类型还提供了一些方便的方法来取出数值。
- 具体可以参考json.Number