JSON的规范中,对于数字类型,并不区分是整型还是浮点型。
对于如下JSON文本:
{
"name": "ethancai",
"fansCount": 9223372036854775807
}
如果反序列化的时候指定明确的结构体和变量类型
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string
FansCount int64
}
func main() {
const jsonStream = `
{"name":"ethancai", "fansCount": 9223372036854775807}
`
var user User // 类型为User
err := json.Unmarshal([]byte(jsonStream), &user)
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%+v \n", user)
}
// Output:
// {Name:ethancai FansCount:9223372036854775807}
如果反序列化不指定结构体类型或者变量类型,则JSON中的数字类型,默认被反序列化成float64
类型:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
func main() {
const jsonStream = `
{"name":"ethancai", "fansCount": 9223372036854775807}
`
var user interface{} // 不指定反序列化的类型
err := json.Unmarshal([]byte(jsonStream), &user)
if err != nil {
fmt.Println("error:", err)
}
m := user.(map[string]interface{})
fansCount := m["fansCount"]
fmt.Printf("%+v \n", reflect.TypeOf(fansCount).Name())
fmt.Printf("%+v \n", fansCount.(float64))
}
// Output:
// float64
// 9.223372036854776e+18
点击这里执行上面的程序
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string
FansCount interface{} // 不指定FansCount变量的类型
}
func main() {
const jsonStream = `
{"name":"ethancai", "fansCount": 9223372036854775807}
`
var user User
err := json.Unmarshal([]byte(jsonStream), &user)
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%+v \n", user)
}
// Output:
// {Name:ethancai FansCount:9.223372036854776e+18}
从上面的程序可以发现,如果fansCount精度比较高,反序列化成float64类型的数值时存在丢失精度的问题。
如何解决这个问题,先看下面程序:
package main
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
func main() {
const jsonStream = `
{"name":"ethancai", "fansCount": 9223372036854775807}
`
decoder := json.NewDecoder(strings.NewReader(jsonStream))
decoder.UseNumber() // UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64.
var user interface{}
if err := decoder.Decode(&user); err != nil {
fmt.Println("error:", err)
return
}
m := user.(map[string]interface{})
fansCount := m["fansCount"]
fmt.Printf("%+v \n", reflect.TypeOf(fansCount).PkgPath() + "." + reflect.TypeOf(fansCount).Name())
v, err := fansCount.(json.Number).Int64()
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("%+v \n", v)
}
// Output:
// encoding/json.Number
// 9223372036854775807
上面的程序,使用了func (*Decoder) UseNumber
方法告诉反序列化JSON
的数字类型的时候,不要直接转换成float64
,而是转换成json.Number
类型。json.Number
内部实现机制是什么,我们来看看源码:
// A Number represents a JSON number literal.
type Number string
// String returns the literal text of the number.
func (n Number) String() string { return string(n) }
// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
return strconv.ParseFloat(string(n), 64)
}
// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
return strconv.ParseInt(string(n), 10, 64)
}
json.Number
本质是字符串,反序列化的时候将JSON的数值先转成json.Number
,其实是一种延迟处理的手段,待后续逻辑需要时候,再把json.Number
转成float64
或者int64
。
对比其它语言,Golang对JSON反序列化处理真是易用性太差(“蛋疼”)。
JavaScript中所有的数值都是双精度浮点数(参考这里),反序列化JSON的时候不用考虑数值类型匹配问题。这里多说两句,JSON的全名JavaScript Object Notation(从名字上就能看出和JavaScript的关系非常紧密),发明人是Douglas Crockford,如果你自称熟悉JavaScript而不知道Douglas Crockford是谁,就像是自称是苹果粉丝却不知道乔布斯是谁。
C#语言的第三方JSON处理library Json.NET反序列化JSON对数值的处理也比Golang要优雅的多:
using System;
using Newtonsoft.Json;
public class Program
{
public static void Main()
{
string json = @"{
'Name': 'Ethan',
'FansCount': 121211,
'Price': 99.99
}";
Product m = JsonConvert.DeserializeObject<Product>(json);
Console.WriteLine(m.FansCount);
Console.WriteLine(m.FansCount.GetType().FullName);
Console.WriteLine(m.Price);
Console.WriteLine(m.Price.GetType().FullName);
}
}
public class Product
{
public string Name
{
get;
set;
}
public object FansCount
{
get;
set;
}
public object Price
{
get;
set;
}
}
// Output:
// 121211
// System.Int64
// 99.99
// System.Double
Json.NET在反序列化的时候自动识别数值是浮点型还是整型,这一点对开发者非常友好。