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

时间:2018-04-02 13:04:31

标签: json rest go

我正在使用以这种方式格式化其响应的API:

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

我现在得到回复的方式是我将响应放在这样的结构中:

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

然后我在收到回复后这样做

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

我只是忽略了这个例子的错误 当我知道数据应该是什么样子以及它应该是什么类型时,我正在编组和解组似乎有点奇怪。

是否有更简单的解决方案,我没有看到或这是正常的事情?

编辑:我应该提一下,响应对象中的Data属性可能会有所不同,具体取决于我正在做的API调用。

1 个答案:

答案 0 :(得分:2)

JSON unmarshaller使用反射来查看它解组的类型。如果未经初始化interface{}作为解组数据的目标,则会将JSON object解组为map[string]interface{}example in playground)。

以下是一些想法。

选项A

如果您知道数据类型,则可以为每种类型定义新的响应结构。例如:

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"`
}

选项B

如果您希望每个类型只有一个Response结构而不是一个结构,那么您可以告诉JSON unmarshaller避免在以后使用data解组json.RawMessage字段。数据类型:

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)
}

输出:

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

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

选项C

@mkopriva在对该问题的评论中指出的第三个选项是使用interface{}作为中间数据类型并将其初始化为已知数据类型。

重点在于中介这个词 - 当然最好避免传递interface{}Rob Pike's Go Proverbs)。这里的用例是允许使用任何数据类型,而不需要多个不同的Response类型。在避免暴露interface{}的方法是完全包装响应,只暴露数据和错误:

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)
  }
}

输出:

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

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