Преобразование интерфейса{} из JSON в один из нескольких конкретных типов

# #go #interface

Вопрос:

Я искал решение этой проблемы и не мог найти его здесь, на SO. Я не уверен, что это возможно, но об этом стоит спросить.

У меня есть интерфейс, и мое приложение считывает данные JSON с сервера. Есть несколько типов, которые могут вернуться в ответ, поэтому я использую interface{} их для использования.

После того , как я разделил байты json на an interface{} , как я могу преобразовать их в конкретный тип? Если это не удастся, я хотел бы проверить следующий тип для разговора, пока у меня не будет успешного разговора.

Это выглядит примерно так (для краткости удален некоторый код)

 type Fooer interface {
    Foo()
}

type A struct { }

func (a *A) Foo() { .. }

type B struct { }

func (b *B) Foo() { .. }

// resp is io.ReadCloser
func heavvyWork(resp) {
    var parsedResp interface{}
    bytesData, err := ioutil.ReadAll(resp)
    json.Unmarshal(bytesData, parsedResp)

    // convert parsedResp to a concrete type, it might be A or B struct
    // try A, if fails, try B.
    ...
    concreteType.Foo()
}
 

Я просмотрел утверждения типа и приведение, но не смог заставить это работать.

Возможно ли это с Go?

Пример содержимого ответа JSON

 A:
{
  "data": {
    "access_key": "AKIA...",
    "secret_key": "xlCs...",
  }
}

B:
{
  "data": {
    "data": {
      "foo": "bar"
    },
    "metadata": {
      ...
    }
  }
}
 

Комментарии:

1. «как я могу преобразовать его в конкретный тип?» — Вручную. Поскольку вы используете interface{} в качестве пункта назначения unmarshal, результатом будет map[string]interface{} или []interface{} или какой-то примитив. Итак , вы берете свое parsedResp утверждение типа к одному из возможных типов, а затем к каждому элементу, если он у него есть, и так далее… а затем из того, что вы собираете, вы строите конкретные типы. Или просто не используйте interface{} в первую очередь.

2. Какая структура? interface{} это не структура, выполнение json.Unmarshal(data, amp;dest) там, где dest имеет тип interface{} без инициализированного динамического типа , волшебным образом не выяснит, что вам нужно A или B . т. е. в нем нет структуры parseResp .

3. Если вы не разберетесь, и interface{} вы получите map[string]interface{} объекты JSON. Вам нужно выполнить преобразование из этого или непосредственно из него в нужный конкретный тип.

4. @Чена. да, если data это так случайно, как вы показываете, и у вас нет возможности заранее узнать, какой конкретный тип представляют байты json, тогда вам придется сделать уродливую вещь, с которой вы возитесь map[string]interface{} .

5. Смотрите это видео для получения некоторых советов по удалению неизвестных типов.

Ответ №1:

У тебя есть варианты. Ни один из них не является совершенно замечательным.

Один из них, как уже упоминалось, состоит в том, чтобы просто заглянуть внутрь карты в поисках ключей. Я привел пример этого здесь. Это должно быть поучительно, но не особенно эффективно или что-то в этом роде.

Другой способ json.RawMessage -немного отложить синтаксический анализ. Я привел пример этого здесь. Обратите внимание, как я прибегаю к элементам указателей различных struct s в декодере. Это раздражает: вы можете захотеть использовать типы данных с указателями только для декодирования и использовать типы без указателей, как только вы выбрали тип. Но это позволяет вам использовать больше пакетов json напрямую (через теги структуры).

Еще один способ-использовать json.NewDecoder для получения потокового декодера и работы с ним. Это, вероятно, был бы самый надежный метод, но и самый сложный.

Комментарии:

1. Спасибо за подробные примеры. Я подумываю о том, чтобы его реорганизовать. Создайте универсальную структуру для анализа json (как в вашем 2-м примере), а затем выясните, какая это структура, на основе ожидаемого содержимого. У вас есть пример этого json.NewDecoder ?

2. Нет, новый декодер выглядел самым сложным из всех, и я просто остановился на этом. 🙂