Как разобрать обернутый объект JSON - PullRequest
0 голосов
/ 03 января 2019

некоторые конечные точки API могут возвращать успешный результат или ошибку, например:

// ok
{
    "status": "ok",
    "payload": {
        "id": 10,
        "title": "Sample"
    },
    "request_id": "lx-VHr4OLm"
}
// error
{
    "status": "error",
    "payload": {
        "message": "internal error"
    },
    "trace_id": "lx-VHr4OLm"
}

Я пытаюсь найти элегантный способ разбора с помощью Go, что-то вроде этого

.... some code
if status == "ok" {
    struct := AppStruct{} // AppStruct contains 2 fields: id and title
    _ := json.Unmarshall(payloadBody, &struct)
    return struct 
} else {
    errorStruct := ErrorStruct{} // contains 1 field for message.
    _ := json.Unmarshall(payloadBody, &errorStruct) 
    return nil, errors.New(errorStruct.Message)
}

Мой текущий код не работает для полезной нагрузки:

var result map[string]interface{}
jsonErr := json.Unmarshal(body, &result)
if jsonErr != nil {
    return nil, jsonErr
}
if result["status"] == "error" {
    errorPayload := result["payload"].(map[string]string)
    return nil, errors.New(errorPayload["message"])
} else if result["status"] == "ok" {
    apiResponse := AppInfo{}
    jsonErr := json.Unmarshal([]byte(result["payload"].(string)), &apiResponse)
    if jsonErr != nil {
        return nil, jsonErr
    }
    return &apiResponse, nil
}

И я получил ошибку во время выполнения в строке json.Unmarshal([]byte(result["payload"].(string)), &apiResponse)

http: обслуживание паники [:: 1]: 51091: преобразование интерфейса: интерфейс {} is интерфейс map [string] {}, а не строка

Конечно, у меня может быть 2 структуры: для успешного ответа и для первой ошибки, но я предполагаю, что это слишком сложный способ решения моей проблемы.

Как правильно разобрать этот JSON?

Ответы [ 2 ]

0 голосов
/ 03 января 2019

Я действительно не уверен, в чем проблема.Стандарт encoding/json не требует, чтобы структура соответствовала всем полям в данных JSON.Это довольно легко сделать с помощью одного простого типа:

type Payload struct {
    ID      int    `json:"id"`
    Title   string `json:"title"`
    Message string `json:"message"`
}

type Response struct {
    Status  string  `json:"status"`
    ID      string  `json:"request_id"`
    TraceID string  `json:"trace_id"`
    Payload Payload `json:"payload"`
}

Затем просто разархивируйте ответ в Response struct:

var resp Response
if err := json.Unmarshal(body, &resp); err != nil {
    return err
}

Затем вы можете просто проверить Status поле, и решить, что делать дальше.Например:

if resp.Status == "error" {
    return fmt.Errorf("invalid response: %s - %s", resp.TraceID, resp.Payload.Message)
}
// handle resp.Payload.ID and resp.Payload.Title fields
return nil

Вы можете переместить эти проверки для поля состояния в функции получателя на объекте ответа, в зависимости от сложности и ваших конкретных потребностей.

Возможно, стоит использовать поля указателядля тех полей, которые не заданы в обычном ответе, и пометьте их параметром omitempty:

type Payload struct {
    ID      int     `json:"id"`
    Title   string  `json:"title"`
    Message *string `json:"message,omitempty"`
}
type Response struct {
    Status  string  `json:"status"`
    ID      string  `json:"request_id"`
    TraceID *string `json:"trace_id,omitempty"`
    Payload Payload `json:"payload"`
}

С такими типами больше не нужно полагаться на жестко закодированные строковые константы для проверкиза ошибки.Вместо этого вы можете легко реализовать более общую проверку, например, так:

func (r Response) IsError() bool {
    return (r.TraceID == nil) // will be set in case of an error response
}

Обновление

Как вы указали в комментариях, тело ответа на самом деле значительно больше, чем 2поля в примере.Конечно, копирование структурных определений или написание функций отображения для сопоставления Payload с уже имеющимся типом немного бессмысленно.

Ответ здесь такой: состав.

type Payload struct {
    AppStruct // embedded the AppStruct type
    Message   *string `json:"message"`
}

Тип Response остается без изменений.Если ответ успешен, вы можете получить AppStruct непосредственно из ответа следующим образом:

appStruct := resp.Payload.AppStruct

Это работает, потому что тип встроен.Обратите внимание, что там нет тегов json.Встроенная структура, по крайней мере, в том, что касается unmarshalling, является частью структуры Payload.Следовательно, все экспортируемые поля этого типа будут распакованы непосредственно в структуру.

0 голосов
/ 03 января 2019

Спасибо https://stackoverflow.com/users/965900/mkopriva за идею использовать json.RawMessage

Мое окончательное решение:

func parsePayload(response []byte, successPayload interface{}) error {
    var result map[string]json.RawMessage
    jsonErr := json.Unmarshal(response, &result)
    if jsonErr != nil {
        return jsonErr
    }
    var status string
    jsonErr = json.Unmarshal(result["status"], &status)
    if jsonErr != nil {
        return jsonErr
    }
    if status == "ok" {
        jsonErr = json.Unmarshal(result["payload"], &successPayload)
        if jsonErr != nil {
            return jsonErr
        }
        return nil
    } else if status == "error" {
        errorPayload := ErrorPayload{}
        jsonErr = json.Unmarshal(result["payload"], &errorPayload)
        if jsonErr != nil {
            return jsonErr
        }
        return errors.New(errorPayload.Message)
    }
    log.Printf("Unknown http result status: %s", status)
    return errors.New("internal error")
}

type ErrorPayload struct {
    Message string `json:"message"`
}

//usage

type AppInfo struct {
    Id    int    `json:"app_id"`
    Title string `json:"app_title"`
}

body := ... // read body
appInfo := AppInfo{}
parseErr := parsePayload(body, &appInfo)
if parseErr != nil {
    return nil, parseErr
}
log.Printf("Parsed app %v", appInfo)
return &appInfo, nil
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...