Go中如何优雅反序列化到类型需事后确定的自定义接口?
优雅处理Go中带内部类型标识的JSON反序列化问题
问题背景
给定如下JSON响应:
{"foo":"bar","object":{"type":"action","data":"somedata"}}
其中object字段可能是多种类型之一,我们定义了统一接口IObject及实现该接口的结构体:
type IObject interface { GetType() string } type Action struct { Type string `json:"type"` Data string `json:"data"` } func (a Action) GetType() string { return "action" } type Activity struct { Type string `json:"type"` Duration int `json:"duration"` } func (a Activity) GetType() string { return "activity" }
同时定义了响应结构体:
type Response struct { Foo string `json:"foo"` Object IObject `json:"object"` }
由于类型信息包含在object内部,无法直接通过标准库自动反序列化到接口类型。当前的解决方案需要将object先反序列化为interface{},转成map[string]interface{}后判断类型,再来回序列化到具体结构体,操作冗余且不够优雅。
优化方案
方案一:直接从map提取字段赋值(无额外序列化)
跳过来回序列化的步骤,直接从解析后的map[string]interface{}中提取字段,手动赋值给对应结构体:
import "errors" func UnmarshalObject(m map[string]interface{}, object *IObject) error { // 先提取并校验type字段 typeVal, ok := m["type"].(string) if !ok { return errors.New("missing or invalid 'type' field in object") } switch typeVal { case "action": action := Action{Type: typeVal} // 处理Data字段的类型转换 if data, ok := m["data"].(string); ok { action.Data = data } *object = action case "activity": activity := Activity{Type: typeVal} // JSON反序列化数字默认是float64,需转换为int if duration, ok := m["duration"].(float64); ok { activity.Duration = int(duration) } *object = activity default: return errors.New("unknown object type: " + typeVal) } return nil } func (r *Response) UnmarshalJSON(data []byte) error { raw := struct { Foo string `json:"foo"` Object map[string]interface{} `json:"object"` }{} if err := json.Unmarshal(data, &raw); err != nil { return err } r.Foo = raw.Foo return UnmarshalObject(raw.Object, &r.Object) }
优点:完全避免序列化/反序列化的额外开销,性能最优;
缺点:需要手动处理每个字段的类型转换和校验,适合字段较少的结构体。
方案二:使用json.RawMessage延迟解析
利用json.RawMessage保留object的原始JSON字节,先解析出type字段,再针对性反序列化到具体结构体:
import ( "encoding/json" "errors" ) func (r *Response) UnmarshalJSON(data []byte) error { // 先解析固定字段和object的原始JSON rawResp := struct { Foo string `json:"foo"` Object json.RawMessage `json:"object"` }{} if err := json.Unmarshal(data, &rawResp); err != nil { return err } r.Foo = rawResp.Foo // 解析object的type字段 var typeMeta struct { Type string `json:"type"` } if err := json.Unmarshal(rawResp.Object, &typeMeta); err != nil { return err } // 根据type反序列化到具体结构体 var obj IObject switch typeMeta.Type { case "action": var action Action if err := json.Unmarshal(rawResp.Object, &action); err != nil { return err } obj = action case "activity": var activity Activity if err := json.Unmarshal(rawResp.Object, &activity); err != nil { return err } obj = activity default: return errors.New("unknown object type: " + typeMeta.Type) } r.Object = obj return nil }
优点:代码更简洁,利用标准库自动处理字段类型转换和校验,适合字段较多的结构体;
缺点:相比方案一多一次反序列化(解析type字段),但远优于原方案的来回序列化。
内容的提问来源于stack exchange,提问作者krypt
相关产品推荐
相关产品推荐

