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"`
}

我的代码中有一个模型(不相关的域详细信息除外)如下所示.FieldN字段的 idea 是支持两种类型,比如MyType1MyType2.它们具有相同的CommonMethod(),但模型非常不同,因此不是要拥有包含更多字段的父类型.

非常出人意料的是,GO无法将JSON解组为接口值.我正在try 使用定制的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
}

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

  1. 在我的例子中,future 字段的数量可能会增加,代码的重复性甚至会比现在更高

  2. 即使是当前版本也需要判断MAP m是否有关键字field_i,否则我只会得到unexpected end of input.这就更加麻烦了.

有没有一种更优雅的方式来做以下事情:

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

谢谢!

Important update:

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

推荐答案

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

}

https://go.dev/play/p/hV3Lgn1RkBz

Json相关问答推荐

我可以使用JQ来缩小数组中的json对象的范围吗?

Python将Pandas转换为嵌套的JSON

NIFI-我需要数组的信息,但只需要第一个信息

Jolt-在数组列表中插入新的字段

NoneType 对象的 Python 类型错误

使用 Power BI 中的 Deneb 视觉效果绘制面积图中的 X 轴日期

在 postgres 14 中将记录转换为所需的 json 格式

jq - 仅在键值对存在的地方打印值

从 PySpark 中的复杂 JSON 文件中高效清除 HTML 实体

解析 JSON API 响应

如何让 JSON.NET 忽略对象关系?

JSON 模式验证

如何在golang中获取 struct 的json字段名称?

在 bash 中将 CSV 转换为 JSON

Android JSON 库的性能和可用性比较

jQuery fullcalendar 发送自定义参数并使用 JSON 刷新日历

使用 Node.js 对 JSON 中的字符串大小有限制吗?

使用 jq 如何用其他名称替换键的名称

如何在 Rails 模型中验证字符串是否为 json

JSON键是否需要唯一?