如何让Go的json.Unmarshal正确处理含结构体的interface{}
解决方案:不修改
Data()返回值的前提下解析JSON到对应结构体 核心问题分析
当Data()返回非指针的结构体实例时,用interface{}接收后,json.Unmarshal(jsonData, &data)会因为Go的JSON包无法推断原始结构体类型,将data解析为map[string]interface{},导致后续无法通过SetData(需要具体DataA/DataB类型)完成赋值。
方案一:扩展接口添加类型实例创建方法
给IDataContainer新增一个NewDataPtr()方法,返回对应Data类型的空指针,让JSON包能识别具体类型完成解析,最后将指针指向的值传给SetData。
修改后的代码
package main import ( "encoding/json" "fmt" ) type IDataContainer interface { Data() interface{} SetData(newData interface{}) NewDataPtr() interface{} // 新增方法,返回对应Data类型的空指针 } type DataA struct { Name string `json:"name"` } type StructA struct { data DataA } func (a *StructA) Data() interface{} { return a.data } func (a *StructA) SetData(newData interface{}) { a.data = newData.(DataA) } func (a *StructA) NewDataPtr() interface{} { return &DataA{} // 返回DataA的空指针 } type DataB struct { Age int `json:"age"` } type StructB struct { data DataB } func (b *StructB) Data() interface{} { return b.data } func (b *StructB) SetData(newData interface{}) { b.data = newData.(DataB) } func (b *StructB) NewDataPtr() interface{} { return &DataB{} // 返回DataB的空指针 } func main() { jsonDatas := [][]byte{[]byte(`{"name":"NEW"}`), []byte(`{"age":30}`)} dataContainers := []IDataContainer{&StructA{}, &StructB{}} for i, jsonData := range jsonDatas { dataContainer := dataContainers[i] // 用新增方法获取对应Data类型的空指针 dataPtr := dataContainer.NewDataPtr() // 直接Unmarshal到指针,JSON包识别具体类型 if err := json.Unmarshal(jsonData, dataPtr); err != nil { panic(err) } // 取指针指向的值,传给SetData switch v := dataPtr.(type) { case *DataA: dataContainer.SetData(*v) case *DataB: dataContainer.SetData(*v) } } // 验证结果 fmt.Println(dataContainers[0].(*StructA).data) // {NEW} fmt.Println(dataContainers[1].(*StructB).data) // {30} }
方案二:利用反射动态创建类型指针解析
如果无法修改IDataContainer接口,可通过反射从Data()返回的实例中获取类型信息,动态创建该类型的指针完成解析,再赋值给容器。
修改后的代码
package main import ( "encoding/json" "fmt" "reflect" ) type IDataContainer interface { Data() interface{} SetData(newData interface{}) } type DataA struct { Name string `json:"name"` } type StructA struct { data DataA } func (a *StructA) Data() interface{} { return a.data } func (a *StructA) SetData(newData interface{}) { a.data = newData.(DataA) } type DataB struct { Age int `json:"age"` } type StructB struct { data DataB } func (b *StructB) Data() interface{} { return b.data } func (b *StructB) SetData(newData interface{}) { b.data = newData.(DataB) } func main() { jsonDatas := [][]byte{[]byte(`{"name":"NEW"}`), []byte(`{"age":30}`)} dataContainers := []IDataContainer{&StructA{}, &StructB{}} for i, jsonData := range jsonDatas { dataContainer := dataContainers[i] // 获取Data()返回值的类型信息 originalData := dataContainer.Data() dataType := reflect.TypeOf(originalData) // 创建该类型的指针实例 dataPtr := reflect.New(dataType).Interface() // 解析JSON到指针 if err := json.Unmarshal(jsonData, dataPtr); err != nil { panic(err) } // 取指针指向的值,传给SetData dataValue := reflect.ValueOf(dataPtr).Elem().Interface() dataContainer.SetData(dataValue) } // 验证结果 fmt.Println(dataContainers[0].(*StructA).data) // {NEW} fmt.Println(dataContainers[1].(*StructB).data) // {30} }
方案说明
- 方案一通过扩展接口明确提供类型指针,代码可读性强、类型安全,适合允许修改接口定义的场景。
- 方案二利用反射绕过接口限制,无需调整原有接口,适合无法修改接口的场景,但反射会带来少量性能开销,代码相对复杂。
内容的提问来源于stack exchange,提问作者kuokongqingyun
相关产品推荐
相关产品推荐

