如何解析包装的json对象

时间:2019-01-03 16:10:54

标签: json go

某些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:接口转换:接口{}是   map [string] interface {},不是字符串

当然,我可能有两种结构:成功响应和错误一,但我认为解决问题的方法太复杂了。

如何以优雅的方式解析此JSON?

2 个答案:

答案 0 :(得分:2)

我真的不确定是什么问题。标准encoding/json不需要该结构匹配JSON数据中的 all 字段。用一个简单的类型来处理它很容易:

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结构中的响应:

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标签。嵌入式结构,至少就解组而言而言,是Payload结构的一部分。因此,该类型的所有导出字段都将直接解组到结构中。

答案 1 :(得分:0)

感谢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