2


I am using an API that has basically 2 returns:
A single object:

{
  "data": {Foo}
}

Or a list of objects:

{
  "data": [
    {Bar},
    {Bar},
    ...
  ]
}

So, I have created 2 envelope class

class Envelope<T> {

    private T data;

    public T getData() {
        return data;
    }
}

class EnvelopeList<T> {

    private List<T> data;

    public List<T> getData() {
        return data;
    }
}

and the service interface

@GET(PATH)
Call<Envelope<Foo>> get();

@GET(PATH_LIST)
Call<EnvelopeList<Bar>> getList();

Using the retrofit 2 config

Retrofit.Builder builder = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create());

everything is working fine...

If I remove the envelope from service like this

@GET(PATH)
Call<Foo> get();

@GET(PATH_LIST)
Call<List<Bar>> getList();

the first that return only an object still working but the one that returns a List gives the error java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

So, I tried to create a converter

public class EnvelopeListConverterFactory extends Converter.Factory {

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(
            final Type type,
            Annotation[] annotations,
            Retrofit retrofit) {

        Type envelopeType = new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                return new Type[]{type};
            }

            @Override
            public Type getRawType() {
                return null;
            }

            @Override
            public Type getOwnerType() {
                return EnvelopeList.class;
            }
        };

        Converter<ResponseBody, EnvelopeList> delegate =
                retrofit.nextResponseBodyConverter(this, envelopeType, annotations);

        return new EnvelopeListConverter(delegate);
    }
}

public class EnvelopeListConverter<T> implements Converter<ResponseBody, List<T>> {
    final Converter<ResponseBody, EnvelopeList<T>> delegate;

    EnvelopeListConverter(Converter<ResponseBody, EnvelopeList<T>> delegate) {
        this.delegate = delegate;
    }

    @Override
    public List<T> convert(ResponseBody responseBody) throws IOException {
        EnvelopeList<T> envelope = delegate.convert(responseBody);
        return envelope.getData();
    }
}

and if I create the retrofit build like this

Retrofit.Builder builder = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addConverterFactory(new EnvelopeListConverterFactory());

I still get the same error as before, but if invert the converter order like

Retrofit.Builder builder = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(new EnvelopeListConverterFactory())
            .addConverterFactory(GsonConverterFactory.create());

the error change to java.lang.IllegalArgumentException: Unable to create a converter for class Bar for method Service.getList and the one that returns a single Object, start to give the same error too.

What can I do to make the service return the Objects without the envelope?

Update

I think I passed the wrong idea on my question. The problem is not that the request can return a single object or a list. I just trying to pass thru the envelope and get the data directly.
I trying to do this http://f2prateek.com/2017/04/21/unwrapping-data-with-retrofit-2/ but it is not working

Saheb
  • 1,392
  • 3
  • 13
  • 33
Fillipe Duoli
  • 1,200
  • 9
  • 17
  • if `{Foo}` and `{Bar}` are of the same type, you can keep the type in the model as an array or list and configure your object deserializer to deserialize single value as array. Like in case of Jackson there is `JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY`. For Gson [this](https://stackoverflow.com/questions/46568964/gson-deserialize-string-or-string-array) can be helpful. – Saheb Mar 31 '18 at 15:48
  • I don't now if I get it right but if I use this all my respondes will be a array (in jackson)? That's not the case. But I will try something with this adapter for Gson. Thx – Fillipe Duoli Mar 31 '18 at 19:45
  • Check this out, almost same as this https://stackoverflow.com/q/41462409/5689605 – Debanjan Apr 01 '18 at 18:21
  • @Debanjan I not explained to well my problem. I added more info on the topic – Fillipe Duoli Apr 03 '18 at 00:25
  • I have a Similar Problem. Did you solve it . I don't want to use Envelope Classes on Retrofit Interfaces like you . Plain object works , List doesn't get made . – user15873 Jun 08 '19 at 17:51
  • @user15873 I added the answer. – Fillipe Duoli Jun 09 '19 at 23:47
  • Note: This converter assumes that all you responses are in the wrapped format and will fail for any response that comes in unwrapped format It says @FillipeDuoli – user15873 Jun 10 '19 at 13:28
  • I get one of the data , unwrapped . {} . Meanwhile other is {data:List, pagination:{page_no,total_items,current}} – user15873 Jun 10 '19 at 13:30
  • I need a converter that Fails and passes the Task to another or Normal Converter. – user15873 Jun 10 '19 at 14:51

1 Answers1

0

I finally found how to create a correct converter class to not need to use the envelope/wrapper on every request.

https://hackernoon.com/retrofit-converter-for-wrapped-responses-8919298a549c

Fillipe Duoli
  • 1,200
  • 9
  • 17