Assume a Go program with several handler functions like this:
type FooRequest struct {
FooField string `json:"foofield"`
// ...
}
type FooResponse struct {
BarField string `json:"barfield"`
// ...
}
func handleFoo(w http.ResponseWriter, r *http.Request) {
var req FooRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// do what actually matters:
foo := DoStuff(req)
baz, err := DoSomething(foo)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp := DoEvenMoreStuff(baz)
// back to boiler plate:
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
How could this code be refactored to avoid the JSON decoding/encoding boiler plate?
I would probably like to see a generic "handle JSON" func and another func handling the actual foo stuff:
func handleJson(w http.ResponseWriter, r *http.Request) {
var req FooRequest // what about this line?
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp, err := handleFooElegantly(req)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func handleFoo(req FooRequest) (FooResponse, error) {
var resp FooResponse
foo := DoStuff(req)
baz, err := DoSomething(foo)
if err != nil {
return resp, err
}
resp = DoEvenMoreStuff(baz)
return resp, nil
}
That leaves us with the problem of telling the JSON decoder the type it should try to decode.
What would be the idiomatic Go way of implementing this?
You can use reflection to eliminate the boilerplate. Here's an example that adapts a func(pointer) (pointer, error) to an http.Handler that handles the JSON encoding and decoding.
type jsonHandler struct {
fn interface{}
}
func (jh jsonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// allocate the input argument
v := reflect.ValueOf(jh.fn)
arg := reflect.New(v.Type().In(0).Elem())
// decode to the argument
if err := json.NewDecoder(r.Body).Decode(arg.Interface()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// invoke the function
out := v.Call([]reflect.Value{arg})
// check error
if err, ok := out[1].Interface().(error); ok && err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// decode result
if err := json.NewEncoder(w).Encode(out[0].Interface()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
Here's an example of how to use this adapter:
type Input struct {
A, B int
}
type Output struct {
Result int
}
func add(in *Input) (*Output, error) {
return &Output{Result: in.A + in.B}, nil
}
handler := jsonHandler{add} // handler satisfies http.Handler
The code will panic if the function does not have a single pointer argument and return a pointer and an error. A more robust and complete implementation should check that the function meets these constraints before returning the handler.
func newHandler(fn interface{)) (http.Handler, error) {
// use reflect to check fn, return error
...
return jsonHandler{fn}, nil
}
The use of reflection in this answer is somewhat similar to an approach used in the standard library.