Декодирование переменной-схемы JSON в Go

Я прошу об этом Go encoding/json, но я думаю, что это также относится к любым другим JSON-библиотекам, которые сопоставляют JSON-капли с объектами на любом языке.

Вот пример. Если вы хотите сократить URL-адрес с помощью API goo.gl URL shortener API, вы получите либо успешный ответ:

{
 "kind": "urlshortener#url",
 "id": "http://goo.gl/fbsS",
 "longUrl": "http://www.google.com/"
}

Или ответ об ошибке:

{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "required",
    "message": "Required",
    "locationType": "parameter",
    "location": "resource.longUrl"
   }
  ],
  "code": 400,
  "message": "Required"
 }
}

Есть ли идиоматический способ справиться с этим - ответ, который может придерживаться двух совершенно разных схем?

Обычно я имею дело с JSON, используя карты/списки; Я знаю, что это возможно в Го. Я мог бы отключить map[string]interface{}, а затем проверить, имеет ли карта "error" в качестве ключа. Но тогда мне придется снова декодировать правильный struct. (Я не прав?)

Я делаю что-то вроде этого. У меня есть один тип для каждого вида ответа:

type successResponse struct {
    Kind string
    Id string
    LongUrl string
}

type errorResponse struct {
    Error struct {
        Errors []struct {
            Domain string
            Reason string
            Message string
            LocationType string
            Location string
        }
        Code int
        Message string
    }
}

И декодирование выглядит следующим образом:

s := new(successResponse)
err := json.Unmarshal(blob, s)
if err == nil {
    // handle success
} else {
    e := new(errorResponse)
    err = json.Unmarshal(blob, e)
    if err == nil {
        // handle error response
    } else {
        // handle actual error
    }
}

Но это кажется уродливым. Как мне подойти к этому?

Ответ 1

Поскольку поля в ваших ответах json отличаются друг от друга, вы можете просто создать одну структуру с объединением всех полей. Json-декодер будет игнорировать поля, которые не присутствуют в строке json, и вы можете проверить существование полей, чтобы узнать, какой тип ответа вы возвращаете.

Ответ 2

Я тоже смутился об этом и подумал, что мне придется расшифровать его снова. Но вы этого не сделаете. Вам просто нужно вывести данные интерфейса {} в соответствующую структуру.

Например, если пакет json поместил это значение в общий interface{}, вы можете преобразовать его в ErrorType с помощью error := val.(ErrorType).

Вы можете использовать foo.(type) в инструкции switch для "делать правильную вещь", если вы разбираете на основе того, какой тип имеет значение.

Я изучаю только на этой неделе, так что это не самый красивый код, но есть примеры в geodns JSON-настройка синтаксического анализа.

Ответ 3

Вы пробовали Go-SimpleJSON? Я думаю, это может решить вашу проблему.

Ответ 4

type Response struct {
    Kind    string
    Id      string
    LongUrl string
    Error   struct {
        Errors []struct {
            Domain       string
            Reason       string
            Message      string
            LocationType string
            Location     string
        }
        Code    int
        Message string
    }
}

s := Response{}
if err := json.Unmarshal(blob, &s); err == nil {
    if s.Error == nil {
        // success
    } else {
        // error
    }
} else {
    // something went wrong
}