json - 将 JSON 动态解码为仅包含基于类型字段的顶级字段的结构

标签 json go unmarshalling

假设我有以下无法修改的事件结构(甚至是标签):

type openAudioStream struct {
    Type     string `json:"type"`
    ID       string `json:"id"`
    Address  string `json:"address"`
    Channels int    `json:"channels"`
}

type openVideoStream struct {
    Type        string `json:"type"`
    ID          string `json:"id"`
    Address     string `json:"address"`
    AspectRatio string `json:"aspectRatio"`
}

我有一个 API 端点,它发出 JSON 字符串(我也无法修改),其中包含映射到这两个结构之一的事件,我无法提前知道它是哪一个,所以我需要以某种方式提取类型字段找出要实例化的结构,然后将 JSON 的其余部分解码到实例化的事件对象中。

我想到的第一个方法是调用 json.Unmarshal两次像这样:

func jsonUnmarshal(payload []byte) (Event, error) {
    eventType := struct {
        Type string
    }{}

    err := json.Unmarshal(payload, &eventType)
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
    }

    var event Event
    switch eventType.Type {
    case "audio":
        event = &openAudioStream{}
        err = json.Unmarshal(payload, event)
    case "video":
        event = &openVideoStream{}
        err = json.Unmarshal(payload, event)
    default:
        err = fmt.Errorf("unrecognised event type: %s", eventType.Type)
    }

    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
    }

    return event, nil
}

虽然这很好用,但遍历 JSON 字符串两次效率低下,所以我认为,也许我可以创建一个联合类型并使用它来解码 JSON,如下所示:

func notWorking(payload []byte) (Event, error) {
    eventUnion := struct {
        Type string `json:"type"`
        openAudioStream
        openVideoStream
    }{}

    err := json.Unmarshal(payload, &eventUnion)
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
    }

    var event Event
    switch eventUnion.Type {
    case "audio":
        event = &extractor.openAudioStream
    case "video":
        event = &extractor.openVideoStream
    default:
        return nil, fmt.Errorf("unrecognised event type: %s", eventUnion.Type)
    }

    return event, nil
}

除了是一个丑陋的黑客之外,如果嵌入式结构包含冲突的字段,这种方法就不起作用。 json 解码器只是 ignores them不产生任何错误。

最后我想起了mapstructure可能会有所帮助,实际上,我可以像这样使用它:

func mapstructureDecode(payload []byte) (Event, error) {
    var unmarshaledPayload map[string]interface{}

    err := json.Unmarshal(payload, &unmarshaledPayload)
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
    }

    var ok bool
    var val interface{}
    var eventType string
    if val, ok = unmarshaledPayload["type"]; ok {
        eventType, ok = val.(string)
    }
    if !ok {
        return nil, fmt.Errorf("failed to determine event type: %v", err)
    }

    var event Event
    switch eventType {
    case "audio":
        event = &openAudioStream{}
        err = mapstructure.Decode(unmarshaledPayload, &event)
    case "video":
        event = &openVideoStream{}
        err = mapstructure.Decode(unmarshaledPayload, event)
    default:
        err = fmt.Errorf("unrecognised event type: %s", eventType)
    }

    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
    }

    return event, nil
}

然而,使用这个库对于这个任务来说似乎有点矫枉过正,它需要添加 mapstructure如果输入 JSON 不遵循标准命名约定,则为结构字段添加标签,如果我有,这是一个问题,例如 aspect_ratio而不是 aspectRatio .

上述实验的完整代码可以在这里找到:https://play.golang.org/p/qTGoV6i8m5P

我很好奇是否有任何其他方法可以使用现有库来解决这个问题。我在想也许是对 json.RawMessage 的一些创造性使用和自定义 UnmarshalJSON方法可能会有所帮助,但如果我在事件结构中只有顶级字段,这似乎没有用。

最佳答案

您可以在组合结构中解码一次,然后根据类型创建新结构。


func jsonUnmarshal(payload []byte) (Event, error) {
    data := struct {
        Type        string
        ID          string
        Address     string
        Channels    int
        AspectRatio string
    }{}
    // Unmarshal in combined stuct
    err := json.Unmarshal(payload, &data)
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
    }
    var event Event
    switch data.Type {
    case "audio":
        // Creating new stuct 
        event = &openAudioStream{Type: data.Type, ID: data.ID, Address: data.Address, Channels: data.Channels}
    case "video":
        event = &openVideoStream{Type: data.Type, ID: data.ID, Address: data.Address, AspectRatio: data.AspectRatio}
    default:
        err = fmt.Errorf("unrecognised event type: %s", data.Type)
    }
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
    }
    return event, nil
}

关于json - 将 JSON 动态解码为仅包含基于类型字段的顶级字段的结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61165306/

相关文章:

javascript - 第 26 行出现意外标记 },但第 26 行 ajax 调用上没有 }

go - Cloudfoundry 密码 - 代码漏洞

golang,使用结构作为函数的参数

json - 无法将外部结构直接调用到 map[string]struct

不使用注释的 Java 代码到 XML/XSD

javascript - 访问不常见 JSON 对象中的数据

ios - 在 Swift 4 中解码 JSON 时遇到问题

ios - 获取 AFNetworking 响应的结果作为 JSON 字符串数组

list - 如何在 Go 中表示变量嵌套列表

go - 为什么要去 json.Unmarshal auto convert interface{} to map