golang 中 json 解析的一些坑

encoding/json包在开发的过程中,占据着相当重要的角色。
然而角色有多重,坑就有多大。下面记录的便是在踩json雷中的辛酸泪(>﹏<)

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

  • 众所周知(其实是最近才知道),golang原生的encoding/json在反序列化的时候,默认情况下会把所有数值类型转成float64

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import (
    "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
2
3
4
type: float64, value: 100
type: <nil>, value: <nil>
type: float64, value: 1.2
type: string, value: 1234

分析

  • 重点在于 json.Unmarshal 这个方法.它的类型转换是这么搞的:

  • To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:

    1
    2
    3
    4
    5
    6
    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
  • 以上解释摘自官方文档

  • 一般情况下是不会有什么问题的,但是,如果我们传进去的是一个大整型(超过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

  • golang的标准json库提供了一种方案:将数值类型直接转成json.Number类型,让用户稍后自己根据需要转成数值。具体的实现是利用json库提供的Decoder:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func 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
2
3
4
反序列化后:    map[string]interface {}{"userID":"1", "config":map[string]interface {}{"target_type":"1", "pid":"1234567890123456789"}}
PID类型: json.Number
PID值: 1234567890123456789
Int64:1234567890123456789
  • 这样,我们的1234567890个亿还是1234567890个亿。可以把json.Number当成字符串,标准库对于这个类型还提供了一些方便的方法来取出数值。
  • 具体可以参考json.Number