JSON请求和响应的通用处理

JSON请求和响应的通用处理,json,go,Json,Go,假设一个Go程序具有以下几个处理程序函数: type FooRequest struct { FooField string `json:"foofield"` // ... } type FooResponse struct { BarField string `json:"barfield"` // ... } func handleFoo(w http.ResponseWriter, r *http.Request) { var req FooR

假设一个Go程序具有以下几个处理程序函数:

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
    }

}
如何重构此代码以避免JSON解码/编码

我可能希望看到一个通用的“handlejson”func和另一个处理实际foo内容的func:

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
}
这就给我们留下了告诉JSON解码器应该尝试解码的类型的问题


实现这一点的惯用方式是什么

您可以使用反射来消除样板文件。下面的示例将func(指针)(指针,错误)调整为处理JSON编码和解码的http.Handler

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
    }
}
下面是如何使用此适配器的示例:

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

如果函数没有单指针参数并返回指针和错误,代码将死机。更健壮、更完整的实现应该在返回处理程序之前检查函数是否满足这些约束

func newHandler(fn interface{)) (http.Handler, error) {
    // use reflect to check fn, return error
    ...

    return jsonHandler{fn}, nil
}

这个答案中反射的用法有些相似。

您可以使用反射来消除样板文件。下面的示例将func(指针)(指针,错误)调整为处理JSON编码和解码的http.Handler

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
    }
}
下面是如何使用此适配器的示例:

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

如果函数没有单指针参数并返回指针和错误,代码将死机。更健壮、更完整的实现应该在返回处理程序之前检查函数是否满足这些约束

func newHandler(fn interface{)) (http.Handler, error) {
    // use reflect to check fn, return error
    ...

    return jsonHandler{fn}, nil
}
在这个答案中,反射的用法有些相似