type MyObj struct {
Field1 string `json:"field_1"`
Field2 int64 `json:"field_2"`
Field3 string `json:"field_3"`
...
FieldK string `json:"field_k"`
FieldN MyInterface `json:"field_n"`
}
Run Code Online (Sandbox Code Playgroud)
我的代码中有一个模型(除了不相关的域详细信息)如下所示。该字段的想法FieldN是支持两种类型,例如MyType1和MyType2。它们具有相同的功能CommonMethod(),但模型非常不同,因此这与具有更多字段的父类型无关。
不出所料,Go 无法将 JSON 解组为接口值。我正在尝试使用自定义UnmarshalJSON()实现,但到目前为止它看起来真的很尴尬:
func (m *MyObj) UnmarshalJSON(data []byte) error {
out := &MyObj{}
var m map[string]json.RawMessage
if err := json.Unmarshal(data, &m); err != nil {
return err
}
if err := json.Unmarshal(m["field_1"], &out.Field1); err != nil {
return err
}
delete(m, "field_1")
if err := json.Unmarshal(m["field_2"], &out.Field2); err != nil {
return err
}
delete(m, "field_2")
if err := json.Unmarshal(m["field_3"], &out.Field3); err != nil {
return err
}
delete(m, "field_3")
... // from 3 to k-1
if err := json.Unmarshal(m["field_k"], &out.FieldK); err != nil {
return err
}
delete(m, "field_k")
var mt1 MyType1
if err := json.Unmarshal(m["field_n"], &mt1); err == nil {
s.FieldN = &mt1
return nil
}
var mt2 MyType2
if err := json.Unmarshal(m["field_n"], &mt2); err == nil {
s.FieldN = &mt2
return nil
}
return nil
}
Run Code Online (Sandbox Code Playgroud)
这种方法的想法是首先解组所有“静态”值,然后处理接口类型。然而,在我看来,它至少存在两个问题:
就我而言,字段的数量将来可能会增加,并且代码将比现在更加重复
即使当前版本也需要检查地图是否m有密钥field_i,否则我只会得到unexpected end of input. 这样就更麻烦了。
有没有更优雅的方法来执行以下操作:
谢谢!
重要更新:
应该注意的是,Field1有效地定义了应使用哪种具体类型FieldN。正如评论中所指出的,这应该大大简化该方法,但我仍然在正确实施方面遇到了一些困难。
使用 json.RawMessage 捕获对象的变化部分。使用应用程序逻辑中确定的类型对原始消息进行解码。
func (m *MyObj) UnmarshalJSON(data []byte) error {
// Declare new type with same fields as MyObj, but
// but no methods. This type is used to avoid
// recursion when unmarshaling a value of type
// Y declared below.
type X MyObj
// Declare a type to capture field_n as a raw message
// and all other fields as normal. The FieldN in
// MyObj is shadowed by the FieldN here.
type Y struct {
*X
FieldN json.RawMessage `json:"field_n"`
}
// Unmarshal field_n to the raw message and all other fields
// to m.
y := Y{X: (*X)(m)}
err := json.Unmarshal(data, &y)
if err != nil {
return err
}
// We now have field_n as a json.RawMessage in y.FieldN.
// We can use whatever logic we want to determine the
// concrete type, create a value of that type, and unmarshal
// to that value.
//
// Here, I assume that field_1 specifies the concrete type.
switch m.Field1 {
case "type1":
m.FieldN = &MyType1{}
case "type2":
m.FieldN = &MyType2{}
default:
return errors.New("unknown field 1")
}
return json.Unmarshal(y.FieldN, m.FieldN)
}
Run Code Online (Sandbox Code Playgroud)
https://go.dev/play/p/hV3Lgn1RkBz