在go中对api请求,一般是先定义一个结构体,然后执行http请求,再使用json.Unmarshal将返回的body反序列化到结构体实例中。比如我们实现一个cmd,执行API查询资源然后显示在终端。
但是在一些场景下,API返回的结构体会因为特性变动而变化,比如新增特性导致返回的结构体中的字段变多,如果不随之修改结构体定义,那么我们使用该结构体时就会导致丢失新增数据。每次取修改结构体有时候也不是特别方便,例如在命令工具中只是简单的显示此字段值,没有必要每次都去修改命令,而且倒是服务间的耦合。
比如如下例子
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonData := `{"user":{
"name": "张三",
"age": 18,
"location": "北京市"
}}`
var s struct {
User *User `json:"user"`
}
if err := json.Unmarshal([]byte(jsonData), &s); err != nil {
fmt.Println("Error:", err)
return
}
user := s.User
fmt.Printf("user: %+v\n", user)
}
表示user的字符串中,user结构体还有location字段,但是我们实际反序列化出来后,这个字段的值丢失了,要想保留localtion字段,就需要在user中新增一个location字段,但是如果下次再增加gender字段,那么就又要修改user结构体
我们可以用接下来介绍的方法解决这个问题。
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Extra map[string]interface{} `json:"-"`
}
func (u *User) UnmarshalJSON(data []byte) error {
type Proxy User
if err := json.Unmarshal(data, (*Proxy)(u)); err != nil {
return err
}
// 解析原始JSON数据,捕获所有未定义的字段
if err := json.Unmarshal(data, &u.Extra); err != nil {
return err
}
var fields []string
rv := reflect.ValueOf(User{})
for i := 0; i < rv.NumField(); i++ {
tag := rv.Type().Field(i).Tag.Get("json")
if tag == "" || tag == "-" {
continue
}
fields = append(fields, tag)
}
// 将未定义的字段放入Extra字段
for _, field := range fields {
delete(u.Extra, field)
}
return nil
}
func main() {
jsonData := `{"user":{
"name": "张三",
"age": 18,
"gender": "male",
"location": "北京市"
}}`
var s struct {
User *User `json:"user"`
}
if err := json.Unmarshal([]byte(jsonData), &s); err != nil {
fmt.Println("Error:", err)
return
}
user := s.User
fmt.Printf("user: %+v\n", user)
}
在上面的例子中,我们在user结构体中定义了一个Extra字段,类型为mapstringany,用它来保存所有未定义的字段和值。
接下来,我们实现了user的UnmarshalJSON方法,这个方法理解起来也比较容易,就是
那么在对user对象调用Unamarshal时,所有未在User中写明的属性都保存在Extra中了,后续的使用就可以从Extra总获取了。
UnmarshalJSON中,有如下关键的一行
type Proxy User
if err := json.Unmarshal(data, (*Proxy)(u)); err != nil {
return err
}
为什么这里要先定一个Use类型别名,而且在json.Unmarshal中要将u转换成(*Proxy)呢
是为了防止递归调用导致死循环。应为如果直接如下调用, 那么这里就会右走到user的UnmarshalJSON方法了。
if err := json.Unmarshal(data, u); err != nil {
return err
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。