处理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.

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