encoding/json

反序列化时的数值处理及float64精度问题

encoding/json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import "fmt"
import "encoding/json"

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
2
3
4
type: float64, value: 100
type: <nil>, value: <nil>
type: float64, value: 1.2
type: string, value: 1234
一般情况下是不会有什么问题的,但是,如果我们传进去的是一个大整型(超过float64定义的范围),那么就粗大事了。举个栗子:
1
2
3
4
5
6
7
8
9
10
11
12
func 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
5
type: float64, value: 100
type: float64, value: 1.2345678901234568e+18
type: <nil>, value: <nil>
type: float64, value: 1.2
type: string, value: 1234
注意到了吗?上面的数字
1234567890123456789
是一个在int64范围内,但是在float64之外的数值。反序列化之后,这个值变成了
123456789012345678
!!!试想一下,本来你手头有1234567890个亿,经过
json.Unmarshal
,就只剩123456789个亿了┭┮﹏┭┮

但是没关系,此事并非不可解。下面我们来看看两种解决方案。

方法一:使用标准库的json.Decoder

json.NumberDecoder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func 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
5
type: json.Number, value: 100
type: json.Number, value: 1234567890123456789
type: <nil>, value: <nil>
type: json.Number, value: 1.2
type: string, value: 1234
这样,我们的1234567890个亿还是1234567890个亿。可以把
json.Number
当成字符串,标准库对于这个类型还提供了一些方便的方法来取出数值。具体可以参考json.Number

以上的代码在:这里

type Number stringtypeNumberstringNumber

方法二:换个库吧

jsoniter是国人写的一个用来替代标准库的json库。这个库允许我们自定义类型解析函数。在其github中的某个issue中提到优先把数值当成int64处理的方法。以下代码注册了一个类型解析函数,这个函数会首先尝试把数值解析成int64,解析失败则将其当成float64处理:

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
import (
"encoding/json"
"github.com/json-iterator/go"
"strconv"
"unsafe"
)

func init() {
decodeNumberAsInt64IfPossible := func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
switch iter.WhatIsNext() {
case jsoniter.NumberValue:
var number json.Number
iter.ReadVal(&number)
i, err := strconv.ParseInt(string(number), 10, 64)
if err == nil {
*(*interface{})(ptr) = i
return
}
f, err := strconv.ParseFloat(string(number), 64)
if err == nil {
*(*interface{})(ptr) = f
return
}
// Not much we can do here.
default:
*(*interface{})(ptr) = iter.Read()
}
}
jsoniter.RegisterTypeDecoderFunc("interface {}", decodeNumberAsInt64IfPossible)
}

Unmarshal

当一切成空,序列化时该怎么办

官方文档有云:

1
2
3
a 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
28
package 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
5
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, [110 117 108 108], err: <nil>
注意哦,这里序列化得到的结果并不是大小为0的byte切片,而是字符串
null
!!!当然,如果不想输出字符串
null
,那么可以修改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package 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>
此时,输出分别是对应类型的零值的字符串表示。例如,空 Map 的序列化值为字符串
{}

所以,如果你只是希望在空的情况下,序列化得出空的结果,那么最好在序列化之前进行一次判空。

参考