To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:
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
encoding/json 是 Go 代码经常使用的包,但是,可能很多人都会忽略下面这段说明:
当 json 解码到 interface 类型的变量值时,会将 JSON numbers(实质是 string 类型,表示整数或浮点数数字字符串)都当作类型 float64 存储。
试想以下代码输出?
完成 Json 解码后,
Timestamp
类型为 float64。这显然是无法让人接受的,就这里来说,时间戳应该是 int64 才对。目前,有两个解决办法:避免使用 interface,而是直接静态类型指定,在大多数情况下,Json 字符串结构都是已知的,静态的。 上面的场景,就可以将时间戳属性定义为
Timestamp int64
。func (*Decoder) UseNumber() 使解码器将数字作为 json.Number 类型, 而不 float64 解码到 interface 变量。
可以看到,json.Number 其实就是字符串类型:
因此,这里其实就是保留原始字符串,延迟解析。在需要的时候,使用提供的函数
Float64()
,Int64()
等转化成对应的类型,其实,这些函数的实现就是使用strconv
包将字符串转化成整型或浮点型。但是,这里引入了一个新的类型 json.Number,会侵入到别的无关的代码中,也就是说,可能会导致,在其它模块,不得不在类型判断时,加入 json.Number case。这种耦合是比较让人难受的。
遗憾的是,目前看来,只有这两种方式了,虽然都不够优雅。
这是个很奇怪的问题,因为技术上来说,将数字字符串分别解析为整型或浮点型并不难实现,Go 编译器就很好的实现了(想想
x:=100
与x:=100.0
的区别); 而且,如果 Json 数字的含义是整型,默认却解析成 float64 就会有精度丢失的问题,因为 int64 比 float64 表示的范围更大。去 Go issues 找了下,也并没有看到合理的解释,难道只是为了实现方便,偷了个懒?真是个奇怪的坑!
相关 issues:
补充
感谢Go 论坛网友 @h12 的指正:
Go 中的 float64 其实等同于
double
类型:范围是
-2^1024 ~ +2^1024
,也即-1.79E+308 ~ +1.79E+308
。精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响:2^52 = 4503599627370496,一共16位,因此,double的精度为15~16位。而 int64 等同于 long, 占8个字节,表示范围:-9223372036854775808 ~ 9223372036854775807
。因此,出现这个坑的原因,是设计上的取舍,为了保证 Json 数字解析安全(不溢出),只能牺牲精度。