Скажем, у меня есть следующие структуры событий, которые я не могу изменить (даже теги):
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 string дважды, так что я подумал, что, может быть, я могу создать тип объединения и использовать его для демонтажа 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 unmarshaler просто игнорирует их без каких-либо ошибок.
Наконец, я вспомнил, что 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
могло бы помочь, но это, кажется, бесполезно, если в структурах событий есть только поля верхнего уровня.