Golang的JSON解码无法解码interface{}类型。

huangapple go评论83阅读模式
英文:

Golang json decoding fails to decode interface{}

问题

我正在使用一个库(go-kit),它要求我指定函数来将请求和响应类型编码/解码为JSON。对于编码,很简单:

func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
    return json.NewEncoder(w).Encode(response)
}

我将这个函数传递给创建HTTP服务器的函数,它可以正常工作。然而,他们提出的请求处理方法是创建一个单独的函数,形式如下:

func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
    var req UppercaseRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        return nil, err
    }
    return req, nil
}

对于我的应用程序中的每个RPC都需要创建这样一个函数。我真的很希望保持我的代码DRY,避免编写成百上千个几乎相同的方法。因此,我尝试编写一个函数来生成解码给定请求类型的闭包:

func DecodeRequest(req interface{}) httptransport.DecodeRequestFunc {
    return func(_ context.Context, r *http.Request) (interface{}, error) {
        if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
            return nil, err
        }
        return req, nil
    }
}

可以这样调用这个函数:

DecodeRequest(UppercaseRequest{})

不幸的是,当我这样做时,JSON解码失败,尽管req的类型实际上是mypackage.UppercaseRequest。我不确定接下来该怎么做。有没有办法避免为每个请求类型编写一个方法?有没有办法在运行时帮助Decode函数理解这个类型是什么?提前谢谢!

这里是一个演示问题的Go Playground链接:
https://play.golang.org/p/GgHsLffp1G

英文:

I'm using a library (go-kit) which requires I specify functions to encode / decode my request and response types to/from JSON. For encoding, it is simple:

func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
	return json.NewEncoder(w).Encode(response)
}

I pass this function to create the HTTP server and it works fine. However, their proposed approach for requests is to make a separate function of the form:

func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
	var req UppercaseRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return nil, err
	}
	return req, nil
}

For every single RPC in my application. I would really like to keep my code DRY and avoid having hundreds of almost identical methods. As such, I attempted to write a function to generate closures that decode the given request type:

func DecodeRequest(req interface{}) httptransport.DecodeRequestFunc {
	return func(_ context.Context, r *http.Request) (interface{}, error) {
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			return nil, err
		}
		return req, nil
	}
}

This function can be called like so:

DecodeRequest(UppercaseRequest{}}

Unfortunately, when I do so, the JSON decoding fails, even though the type of req is in fact mypackage.UppercaseRequest. I'm not sure where to go from here. Is there a way I can avoid having to write a method per request type? Is there some way I can help the Decode function understand what this type is at runtime? Thanks in advance!

Here is a go playground demonstrating the issue:
https://play.golang.org/p/GgHsLffp1G

答案1

得分: 3

根据你展示的代码片段,我认为你遇到了类型断言的问题。我创建了一个示例代码来展示我下面解释的内容。

你将一个 UpperCaseRequest 传递给 DecodeRequest 函数。在这个函数中,参数的类型是 interface{},并且它将该参数的指针传递给了 json 解码器。因此,解码器看到的是一个指向 interface 的指针,而不是指向 UpperCaseRequest 的指针。

这就是为什么解码不正确的原因。然后,尝试对其进行类型断言会失败,因为无法断言两个不同的类型。

因此,在你的代码中,我建议做以下修改:

func DecodeRequest(req interface{}) httptransport.DecodeRequestFunc {
    return func(_ context.Context, r *http.Request) (interface{}, error) {
        // 注意这里移除了 '&'
        if err := json.NewDecoder(r.Body).Decode(req); err != nil {
            return nil, err
        }
        return req, nil
    }
}

并像这样调用该函数:

// 注意这里加上了 '&'
DecodeRequest(&UppercaseRequest{})

希望对你有帮助!

英文:

According to the piece of code you are showing us, I think you are facing a type assertion issue. I created a playground to show you what I explain below.

You're passing a UpperCaseRequest to DecodeRequest func. In this func, the argument is of type interface{}, and it passes a pointer of this argument to the json Decoder. So, The decoder sees a pointer to interface, and not a pointer to UpperCaseRequest.

This is why it's not correctly decoded. And then, trying a type assertion on it fails because asserting two different type is not possible.

So, in your code, I'd suggest:

func DecodeRequest(req interface{}) httptransport.DecodeRequestFunc {
    return func(_ context.Context, r *http.Request) (interface{}, error) {
        // Note the '&' is removed here
        if err := json.NewDecoder(r.Body).Decode(req); err != nil {
            return nil, err
        }
        return req, nil
    }
}

And call this function like this:

// Note the & is placed here.
DecodeRequest(&UppercaseRequest{}}

huangapple
  • 本文由 发表于 2016年8月20日 16:32:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/39052143.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定