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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.