凌云的博客

行胜于言

踩坑日志 - Golang 反序列化 JSON 大整型丢失精度

分类:debug| 发布时间:2024-06-26 09:49:00

问题

在我们项目中有这么一段代码:

var buf = []byte(`{"id": 123, "method": "listDir", "params": {}}`)
var v map[string]interface{}
checkError(json.Unmarshal(buf, &v))
// do something
out, err := json.MarshalIndent(v, "", "  ")
checkError(err)
fmt.Println(string(out))

一直工作正常,输出为:

{
  "id": 123,
  "method": "listDir",
  "params": {}
}

最近需求变更,id 字段变成 64位整型:

var buf = []byte(`{"id": 3424234234234233434, "method": "listDir", "params": {}}`)
var v map[string]interface{}
checkError(json.Unmarshal(buf, &v))
// do something
out, err := json.MarshalIndent(v, "", "  ")
checkError(err)
fmt.Println(string(out))

输出为:

{
  "id": 3424234234234233300,
  "method": "listDir",
  "params": {}
}

发现输出的 id 跟原来的不一致

原因

原因是反序列化为 interface{} 时会将数字类型转换为 float64,我们写个简单的代码验证下:

var buf = []byte(`{"id": 3424234234234233434, "method": "listDir", "params": {}}`)
var v map[string]interface{}
checkError(json.Unmarshal(buf, &v))
fmt.Printf("%v, %s\n", v, reflect.TypeOf(v["id"]).String())

输出为:

map[id:3.4242342342342333e+18 method:listDir params:map[]], float64

解决方案

在 JSON 格式固定的情况下,比较好的方案是明确指定 id 的类型:

var buf = []byte(`{"id": 3424234234234233434, "method": "listDir", "params": {}}`)

type DummyReq struct {
    Id     int64       `json:"id"`
    Method string      `json:"method"`
    Params interface{} `json:"params"`
}
var v DummyReq
checkError(json.Unmarshal(buf, &v))
// do something
fmt.Printf("%v, %s\n", v, reflect.TypeOf(v.Id).String())

输出:

{3424234234234233434 listDir map[]}, int64

对于格式多变的情况需要先解析 JSON,根据不同格式的 JSON 解析成不同的 struct, 比如在我们的例子中 method 不同,里面的params 也不同,需要定义大量的 struct,然后根据 method 的值,反序列化成不同的 struct。

这种方法工作量显然比较大,除了上述方法还可以使用如下方案:

var buf = []byte(`{"id": 3424234234234233434, "method": "listDir", "params": {}}`)
var v map[string]interface{}
reader := bytes.NewReader(buf)
decoder := json.NewDecoder(reader)
decoder.UseNumber()
checkError(decoder.Decode(&v))
fmt.Printf("%v, %s\n", v, reflect.TypeOf(v["id"]).String())

结果为:

map[id:3424234234234233434 method:listDir params:map[]], json.Number

这样反序列化时就会将数字解析成 json.Number 类型,不会发生信息丢失的问题

参考