0

It was my intention to use the HTTP status codes both in the header and the body of the two response structs. Bu that without setting the status code twice as function parameter and again for the struct to avoid redundancy.

The parameter response of JSON() is an interface to allow both structs to be accepted. The compiler throws the following exception:

response.Status undefined (type interface {} has no field or method Status)

because the response field must not have a status attribute. Is there an alternative way to avoid setting the status code twice?

type Response struct {
    Status int         `json:"status"`
    Data   interface{} `json:"data"`
}

type ErrorResponse struct {
    Status int      `json:"status"`
    Errors []string `json:"errors"`
}

func JSON(rw http.ResponseWriter, response interface{}) {
    payload, _ := json.MarshalIndent(response, "", "    ")
    rw.WriteHeader(response.Status)
    ...
}
dlavila
  • 1,204
  • 11
  • 25
user3147268
  • 1,814
  • 7
  • 26
  • 39

2 Answers2

5

The type response in rw.WriteHeader(response.Status) is interface{}. In Go, you need to explicitly assert the type of the underlying struct and then access the field:

func JSON(rw http.ResponseWriter, response interface{}) {
    payload, _ := json.MarshalIndent(response, "", "    ")
    switch r := response.(type) {
    case ErrorResponse:
        rw.WriteHeader(r.Status)
    case Response:
        rw.WriteHeader(r.Status) 
    }
    ...
}

A better and the preferred way to do this however is to define a common interface for your responses, that has a method for getting the status of the response:

type Statuser interface {
    Status() int
}

// You need to rename the fields to avoid name collision.
func (r Response) Status() int { return r.ResStatus }
func (r ErrorResponse) Status() int { return r.ResStatus }

func JSON(rw http.ResponseWriter, response Statuser) {
    payload, _ := json.MarshalIndent(response, "", "    ")
    rw.WriteHeader(response.Status())
    ...
}

And it's better to rename Response to DataResponse and ResponseInterface to Response, IMO.

  • Thanks for the solution. Is it now better so set the status code twice as new paramter and inside the structs or to write two new functions and an interface to achieve the same? – user3147268 May 20 '15 at 17:45
  • nit: `DataResponse` is probably better than `OKResponse`. Another name for the `Status() int` interface would be `Statuser` or just `Status` (the former doesn't sound right but there is a precedence for such `…er` non-word interfaces). – Dave C May 20 '15 at 17:45
  • Thanks @Dave-C. Changed the answer a bit. – Soheil Hassas Yeganeh May 20 '15 at 17:52
  • 1
    @user3147268 IMO it's better to use the interface. You can also create a base common structure that implements `Status()` but that's just an overdo. It's always ok to repeat a little in Go. – Soheil Hassas Yeganeh May 20 '15 at 17:55
  • Then I'll use the interface approach. – user3147268 May 20 '15 at 17:57
  • By the way: would be `Responder` acceptable as interface name by following the -er convention? – user3147268 May 20 '15 at 18:01
  • 1
    Naming is your preference. Go for anything you prefer. As a side note, in Go if an interface wraps a method `M` they call the interface 'M'er. Look at `io.Reader`, `io.Writer`, and `io.ReadWriteCloser` to get an idea. – Soheil Hassas Yeganeh May 20 '15 at 18:02
1

Interfaces don't have attributes, so you need to extract the struct from the interface. To do this you use a type assertion

if response, ok := response.(ErrorResponse); ok {
    rw.WriteHeader(response.Status)
    ...
JimB
  • 104,193
  • 13
  • 262
  • 255