doushi9729 2018-04-02 13:04
浏览 58
已采纳

处理HTTP响应中的接口的最佳方法

I'm using an API that formats its responses in this way:

{
  "err": 0,
  "data": **Other json structure**
}

The way I'm getting a response right now is I'm putting the response in an struct like this:

type Response struct {
   Err int        `json:"err"`
   Data interface{} `json:"data"`
}

and then I'm doing this after getting a response

jsonbytes, _ := json.Marshal(resp.Data)
json.Unmarshal(jsonBytes, &dataStruct)

I'm only ignoring errors for this example.
It seems kinda weird to me that I'm marshaling and unmarshaling when I know what the data is supposed to look like and what type it's supposed to be.

Is there a more simple solution that I'm not seeing or is this a normal thing to do?

Edit: I should probably mention that the Data attribute in the response object can vary depending on what API call I'm doing.

  • 写回答

1条回答 默认 最新

  • doulu6160 2018-04-02 13:41
    关注

    The JSON unmarshaller uses reflection to look at the type it is unmarshalling to. Given an uninitialised interface{} as the destination for the unmarshalled data, a JSON object gets unmarshalled into a map[string]interface{} (example in playground).

    Here are some ideas.

    Option A

    If you know the datatype, you can define a new response struct for each type. Example:

    type FooResponse struct {
      Err  int `json:"err"`
      Data Foo `json:"data"`
    }
    
    type Foo struct {
      FooField string `json:"foofield"`
    }
    
    type BarResponse struct {
      Err  int `json:"err"`
      Data Bar `json:"data"`
    }
    
    type Bar struct {
      BarField string `json:"barfield"`
    }
    

    Option B

    If you prefer to have a single Response struct instead of one per type, you can tell the JSON unmarshaller to avoid unmarshalling the data field until a later time by using the json.RawMessage data type:

    package main
    
    import (
      "encoding/json"
      "fmt"
      "log"
    )
    
    type Response struct {
      Err  int             `json:"err"`
      Data json.RawMessage `json:"data"`
    }
    
    type Foo struct {
      FooField string `json:"foofield"`
    }
    
    type Bar struct {
      BarField string `json:"barfield"`
    }
    
    func main() {
      fooRespJSON := []byte(`{"data":{"foofield":"foo value"}}`)
      barRespJSON := []byte(`{"data":{"barfield":"bar value"}}`)
    
      var (
        resp Response
        foo  Foo
        bar  Bar
      )
    
      // Foo
      if err := json.Unmarshal(fooRespJSON, &resp); err != nil {
        log.Fatal(err)
      }
      if err := json.Unmarshal(resp.Data, &foo); err != nil {
        log.Fatal(err)
      }
      fmt.Println("foo:", foo)
    
      // Bar
      if err := json.Unmarshal(barRespJSON, &resp); err != nil {
        log.Fatal(err)
      }
      if err := json.Unmarshal(resp.Data, &bar); err != nil {
        log.Fatal(err)
      }
      fmt.Println("bar:", bar)
    }
    

    Output:

    foo: {foo value}
    bar: {bar value}
    

    https://play.golang.org/p/Y7D4uhaC4a8

    Option C

    A third option, as pointed out by @mkopriva in a comment on the question, is to use interface{} as an intermediary datatype and pre-initialise this to a known datatype.

    Emphasis lies on the word intermediary -- of course passing around an interface{} is best avoided (Rob Pike's Go Proverbs). The use-case here is to allow any datatype to be used without the need for multiple different Response types. On way to avoid exposing the interface{} is to wrap the response completely, exposing only the data and the error:

    package main
    
    import (
      "encoding/json"
      "fmt"
      "log"
    )
    
    type Foo struct {
      FooField string `json:"foofield"`
    }
    
    type Bar struct {
      BarField string `json:"barfield"`
    }
    
    type Error struct {
      Code int
    }
    
    func (e *Error) Error() string {
      return fmt.Sprintf("error code %d", e.Code)
    }
    
    func unmarshalResponse(data []byte, v interface{}) error {
      resp := struct {
        Err  int         `json:"err"`
        Data interface{} `json:"data"`
      }{Data: v}
    
      if err := json.Unmarshal(data, &resp); err != nil {
        return err
      }
    
      if resp.Err != 0 {
        return &Error{Code: resp.Err}
      }
    
      return nil
    }
    
    func main() {
      fooRespJSON := []byte(`{"data":{"foofield":"foo value"}}`)
      barRespJSON := []byte(`{"data":{"barfield":"bar value"}}`)
      errRespJSON := []byte(`{"err": 123}`)
    
      // Foo
      var foo Foo
      if err := unmarshalResponse(fooRespJSON, &foo); err != nil {
        log.Fatal(err)
      }
      fmt.Println("foo:", foo)
    
      // Bar
      var bar Bar
      if err := unmarshalResponse(barRespJSON, &bar); err != nil {
        log.Fatal(err)
      }
      fmt.Println("bar:", bar)
    
      // Error response
      var v interface{}
      if err := unmarshalResponse(errRespJSON, &v); err != nil {
        log.Fatal(err)
      }
    }
    

    Output:

    foo: {foo value}
    bar: {bar value}
    2009/11/10 23:00:00 error code 123
    

    https://play.golang.org/p/5SVfQGwS-Wy

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 Llama如何调用shell或者Python
  • ¥20 谁能帮我挨个解读这个php语言编的代码什么意思?
  • ¥15 win10权限管理,限制普通用户使用删除功能
  • ¥15 minnio内存占用过大,内存没被回收(Windows环境)
  • ¥65 抖音咸鱼付款链接转码支付宝
  • ¥15 ubuntu22.04上安装ursim-3.15.8.106339遇到的问题
  • ¥15 blast算法(相关搜索:数据库)
  • ¥15 请问有人会紧聚焦相关的matlab知识嘛?
  • ¥15 网络通信安全解决方案
  • ¥50 yalmip+Gurobi