likes
comments
collection
share

GO使用interface{}序列化数字类型的json需要注意的问题

作者站长头像
站长
· 阅读数 13

数字类型统一转换成float64

有时候,我们为了程序的拓展性更强,需要把json转换成功map[string]interface{},便于我们程序的拓展。然后再将interface{}的值转成特定的类型进行处理。举个🌰

var jsonRaw = `{  
    "name": "erik",  
    "age": 12,  
    "score": 95.5  
}`  
  
func TestJsonUnmarshal(t *testing.T) {  
    var userMap = make(map[string]interface{})  
    if err := json.Unmarshal([]byte(jsonRaw), &userMap); err != nil {  
        t.Fatal(err)  
    }  
    if score, ok := userMap["score"].(float64); ok {  
        t.Logf("score conv to float64 success, score info: %v", score)  
    } else {  
        t.Error("score conv to float64 failed")  
    }  
    if age, ok := userMap["age"].(int); ok {  
        t.Logf("age conv to int success, age info: %v", age)  
    } else {  
        t.Error("age conv to int failed")  
    }  
}

上面的示例中,将,学生成绩和年龄,先通过interface{}类型去接收,然后在强制转成intfloat64类型。但是运行上面的示例,程序最终的结果如下:

score conv to float64 success, score info: 95.5
age conv to int failed

score成功的转换成了float64类型,但是对于age却转换失败了。可以看出,go对于json反序列化使用interface{}去接收时,并不是根据数据的大小和特征去设定不同的数据类型的,那么age是什么类型呢?通过IDE打断点发现,age被序列化成了float64

GO使用interface{}序列化数字类型的json需要注意的问题

这种问题的原因是,go如果遇到数据类型,那么他不会判断数据的大小,都会统一将数据类型序列化成float64。 也就是说,一个整形,也会被序列化转成float64类型,然后go又是强类型语言,将float64转成int当然会转换失败。 翻看了go的文档发现,使用interface{}类型去接收时,interface{}的值将会是以下几种

GO使用interface{}序列化数字类型的json需要注意的问题

如何解决

自定义Unmarshal

通过这种方式,对于数字类型,将会被序列化成json.Number的形式。

func Unmarshal(raw []byte, v interface{}) error {  
    decoder := json.NewDecoder(bytes.NewReader(raw))  
    decoder.UseNumber()  
    return decoder.Decode(&v)  
}

GO使用interface{}序列化数字类型的json需要注意的问题 我们再讲值强转成json.Number,json.Number提供了两个Float64(),和Int64()供我们转换使用

func TestJsonUnmarshal(t *testing.T) {  
    var userMap = make(map[string]interface{})  
    if err := Unmarshal([]byte(jsonRaw), &userMap); err != nil {  
        t.Fatal(err)  
    }  
    if score, ok := userMap["score"].(json.Number); ok {  
        sco, _ := score.Float64()  
        t.Logf("score conv to float64 success, score info: %v", sco)  
    } else {  
        t.Error("score conv to float64 failed")  
    }  
    if age, ok := userMap["age"].(json.Number); ok {  
        a, _ := age.Int64()  
        t.Logf("age conv to int success, age info: %v", a)  
    } else {  
        t.Error("age conv to int failed")  
    }  
}

控制台显示正常

score conv to float64 success, score info: 95.5
age conv to int success, age info: 12

json.Number

这个数据类型实际上是一个string类型,Float64,和Int64两个api最终调用的也是go自带的strconv.ParseXXX,源码如下:

// 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去接收值。 eg:

type Student struct {  
    Name string `json:"name"`  
    Age json.Number `json:"age"`  
    Score json.Number `json:"score"`  
}  
  
func TestJsonNumberUnmarshal(t *testing.T) {  
    var user Student  
    if err := json.Unmarshal([]byte(jsonRaw), &user); err != nil {  
    t.Fatal(err)  
    }  
    age, _ := user.Age.Int64()  
    t.Log(age)  
    score, _ := user.Score.Float64()  
    t.Log(score)  
}