Feign: Add possibility to get body from feign.Response as a decoded object

Created on 14 Jun 2016  路  29Comments  路  Source: OpenFeign/feign

Currently decoding of a Response.body() needs to be done manually. It'll be great to get a method body().asObject(MyDtoType.class)

Most helpful comment

Don't you think it will be useful for end users to have such functionality out-of-the-box?

All 29 comments

not sure we want to add that responsibility to Response. I recall retrofit reversing that feature, too. In the mean time, you can remember the codec for these edge cases, right?

The problem is that I don't need to manually care about deserializing responses until I return target object. When I want to wrap it into Response for example to get additionally response code I have to manually read body, which brings imho too much overhead compared to standard use case

yeah I don't think we are optimizing for routine use of Response and decoder together.

It may not seem obvious, but you could make your own Response type and a "decoder" for it. That type could have the method you want, and keep the reference to a real decoder which makes it work.

ex.

class MyDecoder implements Decoder {
  final Decoder delegate;

  public Object decode(Response response, Type type) throws IOException {
    if (type == MyResponse.class) return new MyResponse(response, delegate);
    return delegate.decode(response, type);
  }
}

class MyResponse {
  --snip--
  public int status() {
    return feignResponse.status;
  }

  <T> T asObject(Class<T> clazz) {
    return (T) feignDecoder.decode(feignResponse, clazz);
  }
}

Don't you think it will be useful for end users to have such functionality out-of-the-box?

we don't typically add features only requested once, especially features
that are for edge cases that can be worked around.

closing this issue as won't fix. if it comes up often, we can reconsider

ps some time back I added a HACKING file which explains some of these sentiments.

https://github.com/Netflix/feign/blob/master/HACKING.md

I was expecting Response to behave like that as well.

I have to implement some requests that set headers in the response (for pagination), and I have to resort to using feign.Response to read the headers, but the downside is that I have to handle the ResponseBody myself, which defeats the purpose of using Feign in the first place :(

hmm.. if you are only using feign to decode bodies, maybe yeah there's no
point.

what I mean is that Feign's is mostly abstraction of control apis:
decoding is one of many convenience functions, and most functionality
centers on code for highest value to most.

if you are doing everything else manually and only using feign to
decode bodies, maybe use gson or something directly.

I'm using feign to abstract requests, handle errors, decode responses and circuit breaking (with HystrixFeign) with minimal code. And it excels at that!

The only lacking feature is to retrieve information other than the RequestBody from a response (mainly headers), which makes me resort to feign.Response, which doesn't include some of these convenience functions out of the box.

>

The only lacking feature is to retrieve information other than the
RequestBody from a response (mainly headers), which makes me resort to
feign.Response, which doesn't include some of these convenience functions
out of the box.

Decoder can do this.. it intentionally allows you to access the entire
response for this reason. To make a pagination thing you'd have to compose
a decoder that understands whatever headers you need with a json or
otherwise decoder.

there's a couple examples of pagination in denominator (old code, but still
relevant) here
https://github.com/Netflix/denominator/blob/master/route53/src/main/java/denominator/route53/Route53ZoneApi.java#L134

Hi Adrian,

Could you post / host / point to a complete example of your mentioned solution.

```class MyDecoder implements Decoder {
final Decoder delegate;

public Object decode(Response response, Type type) throws IOException {
if (type == MyResponse.class) return new MyResponse(response, delegate);
return delegate.decode(response, type);
}
}

class MyResponse {
--snip--
public int status() {
return feignResponse.status;
}

T asObject(Class clazz) {
return (T) feignDecoder.decode(feignResponse, clazz);
}
}```

This will be very helpful to those who stumble upon same problem

+1

+1

I'm using this one now:

// feign.Response response
org.apache.commons.io.IOUtils.toString(response.body().asReader())

+1

+1

++

+1

+10086

+1

Since this has been commented on recently, I'll add some additional thoughts on this.

This issue is considered closed as the Decoder provides you with all of the facilities you need to handle the Response in any way you like. The purpose of the Decoder is to parse the Response and return the appropriate model object. This speaks to @adriancole's original response to this issue.

The Response object does provide access to not only the body, but the headers, and original Request.

Finally, building your own Decoder opens up a number of creative ways to handle almost any situation. Let's take the example @matheus208 suggested. In this case you can create a Decoder that also contains another Decoder responsible for parsing the body.

Let say the object you need is a PaginatedResponse, and you need access to the response headers to determine how to populate the additional information in addition to the response body. You can build a CompositeDecoder

class PaginatedResponseDecoder implements Decoder {

    private Decoder responseBodyDecoder;

    public PaginatedResponseDecoder(Decoder responseBodyDecoder) {
        this.responseBodyDecoder = responseBodyDecoder;
    }

    @Override
    public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
        /* parse the headers on the response */
        response.headers();

        /* parse the body */
        this.responseBodyDecoder.decode(response, responseType);

        return null;
    }
}

There are simply too many possible ways to solve problems like this. That's why it's the perspective of Feign to provide interfaces and not implementations. It's our role to empower you, the developer, to handle these scenarios as you see fit, while removing the boilerplate required to consume multiple REST based APIs. I'm happy to talk to more about it, but please do not continue to vote on closed issues.

To me the current implementation of handling the body makes total sense, here's a hint for lost souls:

GsonDecoder gsonDecoder = new GsonDecoder();
CustomPojo body = (CustomPojo) gsonDecoder.decode(response, CustomPojo.class)

Same goes for JacksonDecoder and/or Decoder.Default

Decoder.Default decoder = new Decoder.Default();
String body = (String) decoder.decode(response, String.class)

_Default decoder is pretty simple, this is why I used String.class_

  • Disclaimer: _I dunno how thread safety is handled though,_ you can check that on your side.
  • Disclaimer2: If you want more comfort, do not hesitate to try RestTemplate from Spring

+1

I created a FeignHelper, and hope this may help the person has this problem like me. ObjectMapper come from jackson, you can change it to whatever you like.

public static <T> Optional<T> getResponseBody(Response response, Class<T> klass) {
    try {
        String bodyJson = new BufferedReader(new InputStreamReader(response.body().asInputStream()))
                .lines().parallel().collect(Collectors.joining("\n"));
        return Optional.ofNullable(new ObjectMapper().readValue(bodyJson, klass));
    } catch (IOException e) {
        log.error("Error when read feign response.", e);
        return Optional.empty();
    }
}

+1

There is an example of custom Response class but with typed body and its decoder (eg. JacksonDecoder wrapper). This TypedResponse class combines advantages of automated deserialization and HTTP metadata like headers and status.

    public class TypedResponse<T> {

        private final Response raw;
        private final T body;

        public TypedResponse(Response raw, T body) {
            this.raw = raw;
            this.body = body;
        }

        public T body() {
            return body;
        }

        public Response raw() {
            return raw;
        }
    }
public class TypedResponseDecoder implements Decoder {

    private final Decoder delegate;

    public TypedResponseDecoder(Decoder delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object decode(Response response, Type type) throws IOException {
        Object body = delegate.decode(response, getActualTypeArgument(type));
        return new TypedResponse<>(response, body);
    }

    private Type getActualTypeArgument(Type type) {
        ParameterizedType pt = (ParameterizedType) type;
        return pt.getActualTypeArguments().length > 0 ? pt.getActualTypeArguments()[0] : null;
    }

+1

Was this page helpful?
0 / 5 - 0 ratings