【问题标题】:Generic handling of JSON requests and responses通用处理 JSON 请求和响应
【发布时间】:2016-10-03 06:07:06
【问题描述】:

假设一个 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 解码/编码样板?

我可能希望看到一个通用的“处理 JSON”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 解码器它应该尝试解码的类型的问题。

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

【问题讨论】:

    标签: json go


    【解决方案1】:

    您可以使用反射来消除样板。这是一个将 func(pointer) (pointer, error) 适配到处理 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
    

    working playground example

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

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

    这个答案中反射的使用有点类似于to an approach used in the standard library

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-27
      • 2011-02-23
      • 2021-12-02
      • 1970-01-01
      • 1970-01-01
      • 2011-02-13
      • 2016-08-21
      • 2013-06-22
      相关资源
      最近更新 更多