Go中json.Unmarshal对数字类型的处理

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值