本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1、前言
一次在json字符串反序列化为interface{}时遇到的一个问题。 首先先看这样一段代码:
import (
"encoding/json"
"fmt"
"testing"
)
type Demo struct {
Errno int32 `json:"errno"`
ErrMsg string `json:"errmsg"`
Data []string `json:"data"`
}
type Result struct {
Errno int32 `json:"errno"`
ErrMsg string `json:"errmsg"`
Data interface{} `json:"data"`
}
func TestA(t *testing.T) {
demo := Demo{
Errno: 0,
ErrMsg: "success",
Data: []string{"hello", "world"},
}
// 序列化Demo结构体
s,_ := json.Marshal(demo)
var res Result
// 反序列化
_ = json.Unmarshal(s, &res)
data, ok := res.Data.([]string)
fmt.Println("data:", data) // []
fmt.Println("ok:", ok) // false
}
反序列化时,用interface{}
类型接收Data字段数据。在转换为原来的[]string
类型时,发现转换失败了。
打印反序列化后的Data字段类型是:[]interface{}
。
fmt.Printf("%T\n", res.Data) // []interface {}
2、分析
由打印结果引出两问题:
- 为什么
[]interface{}
类型转换为[]string
类型时会失败? - 为什么原来
[]string
类型的字段,json先序列化再反序列化,类型就会变成[]interface{}
?
我们在IDE做个小测试,代码如下👇:
var dataSlice = []string{
"a",
"b",
}
var interfaceSlice []interface{} = dataSlice
结果这段代码会在IDE中会直接报错:
Cannot use 'dataSlice' (type []string) as the type []interface{}
。
这个与我们预期想法不一致,理论上可以把任何类型赋值给interface{}
,但是上面👆case却发现不能把任何类型的切片赋值到[]interface{}
。
3、原因
3.1、类型转化失败
[]interface{}
类型不是interface{}
类型,它是一个切片,切片元素的类型恰好是interface{}
。[]interface{}
类型变量拥有特定的内存结构,这在编译时就已经决定了。
每个interface{}
占两个字(word),一个字用于存放interface
存放的类型,另一个字用于存放实际数据或者是指向数据的指针。于是长度为N的[]interface{}
类型切片背后是一个N*2
字长的一块数据。
这与一般的[]MyType
类型切片不同,相同长度的[]MyType
切片背后的数据块大小为N*sizeof(MyType)
字长。
正是这两个原因决定了不能直接将某些[]MyType
切片赋值给[]interface{}
, 他们背后代表的数据意义不同。
同理我们也无法将[]interface{}
类型转换为[]string
类型。
3.2、序列化后反序列化失败
这个问题存在的前提是,我们用一个interface{}
类型来接收json反序列化后的字段。如果要将一个json字符串反序列化到一个interface
字段上,则会返回以下集合对应的类型:
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
也就是对于所有的json array类型,都将得到[]interface{}
类型。
4、解决方案
通过以上对问题的定位和分析,当我们需要得到反序列化后slice里面的string类型时,只能通过遍历[]interface{}
类型切片,循环地将每一个元素转换为string
类型了,否则就需要明确地使用[]string
来接收反序列化的字段了。
for example:
var res Result
_ = json.Unmarshal(s, &res)
data1, ok := res.Data.([]interface{})
for _, item := range data1 {
s, ok := item.(string)
fmt.Println(s, ok)
}
// log
//hello true
//world true
参考链接🔗:
1、github.com/golang/go/w…
2、pkg.go.dev/encoding/js…