1

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

4
  • What's the json decode errror? A type switch may help or the reflect package has lots of tools for runtime type idenfication. Commented Aug 20, 2016 at 8:45
  • @Sridhar If I call DecodeRequest(CountRequest{}) I get "http: panic serving [::1]:55992: interface conversion: interface is map[string]interface {}, not user.CountRequest" and if i change the function to use ...Decode(req) and call DecodeRequest(&CountRequest{}) I get "http: panic serving [::1]:56375: interface conversion: interface is *user.CountRequest, not user.CountRequest" Commented Aug 20, 2016 at 8:57
  • What's CountRequest ? A map[string]interface{} ? You really should abide by this . Maybe try Decode(Req) and call DecodeRequest(CountRequest{}) ? Commented Aug 20, 2016 at 9:19
  • @Sridhar it's just a struct: type CountRequest struct { S string json:"s" } I am not sure where the map[string]interface{} is coming from. When I try Decode(req) and call DecodeRequest(CountRequest{}) I get: interface conversion: interface is *user.CountRequest, not user.CountRequest Commented Aug 20, 2016 at 9:34

1 Answer 1

3

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{}} 
Sign up to request clarification or add additional context in comments.

4 Comments

Unfortunately, this doesn't work for me. When I try it, the error I get is: http: panic serving [::1]:58709: interface conversion: interface is *user.UppercaseRequest, not user.UppercaseRequest
Oh! I think i have an idea - maybe the issue isn't with the json decoder, but rather that i'm returning a pointer instead of the actual struct. I think I need to return *req, nil. But then how can I cast an interface to a pointer to an interface?
Unfortunately neither worked - check the go playground I added which replicates the issue.
I updated your playground with a working solution (still based on what I am saying in my answer). I added the type assertion so you can see how to use it. play.golang.org/p/xjbfaoqwLg

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.