数字类型统一转换成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{}
类型去接收,然后在强制转成int
和float64
类型。但是运行上面的示例,程序最终的结果如下:
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如果遇到数据类型,那么他不会判断数据的大小,都会统一将数据类型序列化成float64。 也就是说,一个整形,也会被序列化转成float64类型,然后go又是强类型语言,将float64转成int当然会转换失败。 翻看了go的文档发现,使用interface{}类型去接收时,interface{}的值将会是以下几种
如何解决
自定义Unmarshal
通过这种方式,对于数字类型,将会被序列化成json.Number
的形式。
func Unmarshal(raw []byte, v interface{}) error {
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
return decoder.Decode(&v)
}
我们再讲值强转成
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)
}