encoding/json
包在web开发的过程中,占据着相当重要的角色。然而角色有多重,坑就有多大。下面记录的便是在踩json雷中的辛酸泪(>﹏<)
反序列化时的数值处理及float64精度问题
众所周知(其实是最近才知道),golang原生的encoding/json
在反序列化的时候,默认情况下会把所有数值类型转成float64类型。
1 | import "fmt" |
调用上面的函数会输出: 1
2
3
4type: float64, value: 100
type: <nil>, value: <nil>
type: float64, value: 1.2
type: string, value: 12341
2
3
4
5
6
7
8
9
10
11
12func test_std_json_large(){
var m []interface{}
if err := json.Unmarshal([]byte(`[100, 1234567890123456789, 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
2
3
4
5type: float64, value: 100
type: float64, value: 1.2345678901234568e+18
type: <nil>, value: <nil>
type: float64, value: 1.2
type: string, value: 12341234567890123456789
是一个在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
12
13
14
15func test_std_json_large_withNumber(){
var m []interface{}
decoder := json.NewDecoder(strings.NewReader(`[100, 1234567890123456789, null, 1.2, "1234"]`))
decoder.UseNumber()
if err := decoder.Decode(&m); err == nil {
for _, v := range m {
fmt.Printf("type: %T, value: %v\n",v, v )
}
} else {
fmt.Println("Unmarshal error: ", err)
}
}1
2
3
4
5type: json.Number, value: 100
type: json.Number, value: 1234567890123456789
type: <nil>, value: <nil>
type: json.Number, value: 1.2
type: string, value: 1234json.Number
当成字符串,标准库对于这个类型还提供了一些方便的方法来取出数值。具体可以参考json.Number
以上的代码在:这里
一般来说,这个方法已经很好的解决了float64的精度问题。但是我们还可以找个替代方案。 > 因为type Number string
,所以之前以为使用这种方法,在反序列化之后,无法区分数值和数值字符串。在写这篇文章的时候才突然想起来,在golang中,使用关键字type
定义的是新的类型。也就是说,在golang看来,Number
和string
是两种不一样的类型。但是如果使用Number
,在取值的时候,必不可免需要进行类型判断。所以才会考虑找个替代库。
方法二:换个库吧
jsoniter是国人写的一个用来替代标准库的json库。这个库允许我们自定义类型解析函数。在其github中的某个issue中提到优先把数值当成int64处理的方法。以下代码注册了一个类型解析函数,这个函数会首先尝试把数值解析成int64,解析失败则将其当成float64处理:
1 | import ( |
之后,我们就可以像使用标准库那样,调用jsoniter的Unmarshal
方法进行反序列化了。
当一切成空,序列化时该怎么办
官方文档有云: 1
2
3a nil slice encodes as the null JSON value.
A nil pointer encodes as the null JSON value.
A nil interface value encodes as the null JSON value.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
28package main
import (
"fmt"
"encoding/json"
)
func main() {
// map
var sm map[string]interface{}
sb, err := json.Marshal(sm)
fmt.Printf("%s, %v, err: %v\n", string(sb), sb, err)
// slice
var ss []string
sb, err = json.Marshal(ss)
fmt.Printf("%s, %v, err: %v\n", string(sb), sb, err)
var sbs []byte
sb, err = json.Marshal(sbs)
fmt.Printf("%s, %v, err: %v\n", string(sb), sb, err)
// pointer
var sp *string
sb, err = json.Marshal(sp)
fmt.Printf("%s, %v, err: %v\n", string(sb), sb, err)
// interface
var si interface{}
sb, err = json.Marshal(si)
fmt.Printf("%s, %v, err: %v\n", string(sb), sb, err)
}1
2
3
4
5null, [110 117 108 108], err: <nil>
null, [110 117 108 108], err: <nil>
null, [110 117 108 108], err: <nil>
null, [110 117 108 108], err: <nil>
null, [110 117 108 108], err: <nil>null
!!!当然,如果不想输出字符串null
,那么可以修改为: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package main
import (
"fmt"
"encoding/json"
)
func main() {
// map
sm := make(map[string]interface{}, 0)
sb, err := json.Marshal(sm)
fmt.Printf("%s, %v, err: %v\n", string(sb), sb, err)
// slice
ss := make([]string, 0)
sb, err = json.Marshal(ss)
fmt.Printf("%s, %v, err: %v\n", string(sb), sb, err)
// bytes
sbs := make([]byte, 0)
sb, err = json.Marshal(sbs)
fmt.Printf("%s, %v, err: %v\n", string(sb), sb, err)
}1
2
3{}, [123 125], err: <nil>
[], [91 93], err: <nil>
"", [34 34], err: <nil>{}
所以,如果你只是希望在空的情况下,序列化得出空的结果,那么最好在序列化之前进行一次判空。