go中如何反序列化interface对象
工作中经常要调用别的业务团队提供的API接口。在被调方未提供SDK的情况下,需要手动执行http请求以获取相关数据。同一业务不同API的响应,基本都会遵循统一个数据格式,抽象如下:
type Base struct {
Data interface{} `json:"data"`
}
type Info struct {
Field1 int `json:"field1"`
Insides []struct {
Field2 int `json:"field2"`
} `json:"insides"`
}
如此,就免去了对于不同接口通用外部Base
部分的重复定义,只需定义各自的Info
字段并将其赋值给Base.Data
即可。但开发时很容易误用值赋值而非指针赋值,就会导致程序panic。
问题的复现代码很简单,如下:
func main() {
str := "{\"data\":{\"field1\":1,\"insides\":[{\"field2\":2}]}}"
base := Base{
Data: Info{},
}
_ = json.Unmarshal([]byte(str), &base)
out := base.Data.(Info)
log.Printf("%+v", out)
}
输出结果如下:
❯ go run leetcode.go
panic: interface conversion: interface {} is map[string]interface {}, not main.Outside
goroutine 1 [running]:
main.main()
/Users/brick/personalspace/golearn/leetcode/leetcode.go:139 +0x14d
exit status 2
panic
提示信息很明显,内存中Base.Data
字段为map[string]interface {}
,而非main.Outside
。赋值后Outside
每个字段都有json tag
,为何go无法自动按照tag反序列化呢?json.Unmarshal
源码注释中对于此问题有明确说明:
// To unmarshal JSON into an interface value,
// Unmarshal stores one of these in the interface value:
//
// 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 objects
对象,会反序列为map[string]interface{}
对象。这样在使用interface
断言系统自然就会panic。
既然无法直接使用Info{}
类型给Base.Data
赋值,那应该如何编写代码才能满足可扩展性的需求呢?测试发现可使用&Info{}
类型,打破Unmarshal
对于类型的约束。目前尝试阅读源码,但并未理清相关逻辑。更新代码如下:
func main() {
str := "{\"data\":{\"field1\":1,\"insides\":[{\"field2\":2}]}}"
base := Base{
Data: &Info{},
}
_ = json.Unmarshal([]byte(str), &base)
out := base.Data.(*Info)
log.Printf("%+v", out)
}
输出如下:
❯ go run leetcode.go
2022/12/11 20:51:45 *main.Info
2022/12/11 20:51:45 &{Field1:1 Insides:[{Field2:2}]}