在这种情况下,如何实现 UnmarshalJSON 并且只为一个接口字段定义特殊行为?

Don*_*per 4 json go

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是支持两种类型,例如MyType1MyType2。它们具有相同的功能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)

这种方法的想法是首先解组所有“静态”值,然后处理接口类型。然而,在我看来,它至少存在两个问题:

  1. 就我而言,字段的数量将来可能会增加,并且代码将比现在更加重复

  2. 即使当前版本也需要检查地图是否m有密钥field_i,否则我只会得到unexpected end of input. 这样就更麻烦了。

有没有更优雅的方法来执行以下操作:

  • 解组所有具有静态类型的字段
  • 处理唯一的特殊接口类型值

谢谢!

重要更新:

应该注意的是,Field1有效地定义了应使用哪种具体类型FieldN。正如评论中所指出的,这应该大大简化该方法,但我仍然在正确实施方面遇到了一些困难。

Jew*_*ger 5

使用 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