假设我有以下无法修改的事件结构(甚至是标签):
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/