QuXiao's Blog

Life && Tech && Thoughts

Golang Json 解析中的小坑

Written on

背景

在设计模块之间进行通信协议时,使用了 TCP + JSON 的方案,JSON是自解释的,再加上Golang本身的 Marshal/Unmarshal 就能十分方便的把JSON string转换为Struct,所以开发、测试直至上线的过程中都没有遇到什么问题,模块就在线上一直愉快的运行着,直到某一天……

某一天突然发现线上数据有问题了,检查了项目中的各个模块,在一个模块中发现了异常的log:

strconv.ParseInt: parsing "1.219478e+06": invalid syntax

奇怪,一个本来是存放 int64 的字段,怎么变成科学计数了?最后发现原因是:设计通信的JSON时,设置了一个『扩展』字段,是一个KV的形式,可以让不同业务在这个字段中添加自己需要的字段。类似于一下JSON:

{
    "extra": {
        "k1": "some string",
        "k2": 0
    }
}

因为无法确定key的名称和value的类型,所以映射Golang的Struct的时候,只能使用 map[string]interface{} 。不过,对于某个业务,业务方肯定知道某一个key对应的value是什么类型,因此就采用将这个key以string类型打印出来再解析的方式,例如:

v2, err := strconv.Atoi(fmt.Sprintf("%v", extra["k2"]))
// ...

测试验证

后来根据测试发现:如果对一个 map[string]interface{} 赋值整数,转换为JSON再输出为string,超过6位就会使用科学记数法,而无法Struct中明确是一个整数类型,始终都是整数的输出格式

测试程序:

package main

import (
    "encoding/json"
    "fmt"
)

type T struct {
    Int int64
    Map map[string]interface{}
}

func main() {
    var v1, v2 int64 = 123456, 1234567
    test(v1)
    test(v2)
}

func test(val int64) {
    t, t2 := T{Map: make(map[string]interface{})}, T{Map: make(map[string]interface{})}
    t.Int = val
    t.Map["val"] = val
    fmt.Println(fmt.Sprintf("before: %v", t))

    jsonBytes, _ := json.Marshal(t)
    json.Unmarshal(jsonBytes, &t2)

    fmt.Println(fmt.Sprintf("after: %v", t2))
}

输出结果:

before: {123456 map[val:123456]}
after: {123456 map[val:123456]}
before: {1234567 map[val:1234567]}
after: {1234567 map[val:1.234567e+06]}

解决方案

解决方法比较简单暴力,就是把这个string当做float64来进行解析,然后再转换为int类型。是个小坑,记录一下。想了想,其实还是UT做得不够充分导致了,各种编码质量、流程还得继续加强。

-- EOF --

comments powered by Disqus