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在反序列化的时候自动识别数值是浮点型还是整型,这一点对开发者非常友好。