JSON请求和响应的通用处理

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

working playground example

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.