某些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?
答案 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