1

I have this JSON body:

[
    {
        "id": 0,
        "field1": "10",
        "field2": "22"
    },
    {
        "id": 1,
        "field1": "11",
        "field2": "23"
    }
]

My pojoItem:

@AutoValue
public abstract class PojoItem{

    @SerializedName("field1")
    public abstract String field1();

    @SerializedName("id")
    public abstract int id();

    @SerializedName("field2")
    public abstract String field2();

}

And my pojoItemList:

@AutoValue
public abstract class PojoItemList{

    public abstract List< PojoItem > itemList();

    public static TypeAdapter<PojoItemList> typeAdapter(Gson gson) {
        return new AutoValue_PojoItemList.GsonTypeAdapter(gson);
    }
}

I have AutoValueGsonFactory:

@GsonTypeAdapterFactory
public abstract class AutoValueGsonFactory implements TypeAdapterFactory {

    // Static factory method to access the package
    // private generated implementation
    public static TypeAdapterFactory create() {
        return new AutoValueGson_AutoValueGsonFactory();
    }
}

I'm using Retrofit with RxJava. I got this exception:

java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY

How can I setup my POJO to read JSON as an array of objects not as collection?

Maher Abuthraa
  • 17,493
  • 11
  • 81
  • 103

3 Answers3

2

The problem is that you are giving a name itemList for your list, which is not existent in the response.

Retrofit should work with something like this:

public class Item {

    @SerializedName("field1")
    public String field1;

    @SerializedName("id")
    public int id;

    @SerializedName("field2")
    public String field2;

}

And then, when you define Retrofit's interface use something like this:

@GET("/path")
Single<List<Item>> getItems(); 
ViliusK
  • 11,345
  • 4
  • 67
  • 71
  • 1
    Yes, this solution works, but the OP is asking for a solution in the context of AutoValue and AutoValue Gson extension (both are source code generators that can generate value types, their `toString`s with `equals`s/`hashCode`s along with Gson-compliant type adapters). – Lyubomyr Shaydariv Feb 15 '17 at 09:00
  • 1
    I was looking basically to a solution worked with RxJava .. your code helped me a lot to understand where is my problem. +1 – Maher Abuthraa Feb 15 '17 at 13:53
1

AutoValue Gson does not generate Gson type adapters for array (at least from what I've seen from its source code). Thus Gson expects a List instance. Note that your list data model conflicts with Gson defaults, and with what AutoValue Gson generates. Your have two solutions.

Solution 1: Do not use PojoItemList

Why: arrays/lists do not need anything like itemsList(). I'm not really sure you'll ever get any other auto-generated values in PojoItemList except itemList(). List<PojoItem> is really enough to make it work. So, a raw Gson code that works with lists efficiently:

final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(AutoValueGsonFactory.create())
        .create();
final TypeToken<List<PojoItem>> pojoItemListTypeToken = new TypeToken<List<PojoItem>>() {
    };
out.println(gson.<List<PojoItem>>fromJson(JSON, pojoItemListTypeToken.getType()));

As far as I understand, Retrofit will pass the type to Gson itself, and, accordingly, your Retrofitted service must not use PojoItemList in this case, and use List<PojoItem>:

interface IService {

    List<PojoItem> getPojoItems();

}

Note that you must add a type adapter for PojoItem that can be generated by AutoValue Gson:

@AutoValue
public abstract class PojoItem {

    ...

    public static TypeAdapter<PojoItem> typeAdapter(final Gson gson) {
        return new AutoValue_PojoItem.GsonTypeAdapter(gson);
    }

}

If the type adapter is not generated and registered, Gson won't be able to create a PojoItem instance:

java.lang.RuntimeException: Failed to invoke public q42240399.PojoItem() with no args

Solution 2: Do AutoValue Gson job yourself

If for some reason you want to use PojoItemList, then you have to write your custom TypeAdapter because, as I mentioned above, AutoValue Gson does not generate array type adapters (I couldn't see any beginArray invocations, though).

@AutoValue
public abstract class PojoItemList {

    public abstract List<PojoItem> itemList();

    public static TypeAdapter<PojoItemList> typeAdapter(final Gson gson) {
        // Get the original PojoItem type adapter you can use below
        final TypeAdapter<PojoItem> pojoItemTypeAdapter = gson.getAdapter(PojoItem.class);
        return new TypeAdapter<PojoItemList>() {
            @Override
            public void write(final JsonWriter out, final PojoItemList pojoItemList) {
                out.beginArray();
                for ( final PojoItem pojoItem : pojoItemList.itemList() ) {
                    pojoItemTypeAdapter.write(out, pojoItem);
                }
                out.endArray();
            }

            @Override
            public PojoItemList read(final JsonReader in)
                    throws IOException {
                final List<PojoItem> pojoItems = new ArrayList<>();
                // The first token must be [
                in.beginArray();
                // And read until ] is found
                while ( in.peek() != END_ARRAY ) {
                    // Delegate parsing to the PojoItem type adapter for each array element
                    pojoItems.add(pojoItemTypeAdapter.read(in));
                }
                // The last token must be ]
                in.endArray();
                // Construct the PojoItemList value
                return new AutoValue_PojoItemList(pojoItems);
            }
        };
    }

}

You might want to ask the AutoValue Gson extension authors for implementing an array-compliant extension. However, I think that solution #1 is much better for several reasons.

Both solutions work and will produce:

  • For List<PojoItem>:
[PojoItem{field1=10, id=0, field2=22}, PojoItem{field1=11, id=1, field2=23}]
  • For PojoItemList:
PojoItemList{itemList=[PojoItem{field1=10, id=0, field2=22}, PojoItem{field1=11, id=1, field2=23}]}
Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
  • You are right about not using PojoItemList.. I removed it. but I am looking for simple solution using AutoValue and retrofit without defining more objects or type .. I learned new things from your code ..thank you. – Maher Abuthraa Feb 15 '17 at 13:50
  • @MaherAbuthraa Then you should go with solution #1: remove `PojoItemList` and add the `PojoItem` type adapter in order to let Gson instantiate `PojoItem` instances. – Lyubomyr Shaydariv Feb 15 '17 at 13:51
0

I could make it work by the end with/without RxJava Here are two ways:

To make it clear I will provide API and complete Retrofit code used to achieve that goal:

AppApi:

public interface AppApi {
    @GET("path/path")
    Observable<List<PojoItem>> getItemsbyRxJava();


    @GET("path/path")
    Call<List<PojoItem>> getItemsbyRetrofit();
}
  1. Solution with RxJava:

    GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(new GsonBuilder().registerTypeAdapterFactory(AutoValueGsonFactory.create()).create());
    OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
    Retrofit retrofitClient = new Retrofit.Builder()
            .baseUrl("http://[domain]/path/")
            .addConverterFactory(gsonConverterFactory)
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .client(httpClient.build())
            .build();
    
    AppApi appApi = retrofitClient.create(AppApi.class);
    
    appApi.getItemsbyRxJava()
            .subscribeOn(Schedulers.computation())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<List<PojoItem>>() {
                @Override
                public void onCompleted() {
                    Log.d(TAG, "completed");
                }
    
                @Override
                public void onError(Throwable e) {
                    Log.e(TAG, e.getMessage());
                }
    
                @Override
                public void onNext(List<PojoItem> list) {
                    for (PojoItem pojoItem : list) {
                        Log.d(TAG, pojoItem.field1() + "," + pojoItem.field2());
                    }
                }
            });
    
  2. Solution with Retrofit only:

    OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
    Retrofit retrofitClient = new Retrofit.Builder()
            .baseUrl("http://[domain]/path/")
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .client(httpClient.build())
            .build();
    AppApi appApi = retrofitClient.create(AppApi.class);
    Call<List<PojoItem>> call = appApi.getItemsbyRetrofit();
    call.enqueue(new Callback<List<PojoItem>>() {
        @Override
        public void onResponse(Call<List<PojoItem>> call, Response<List<PojoItem>> response) {
            for (PojoItem pojoItem : response.body()) {
                Log.d(TAG, pojoItem.field1() + "," + pojoItem.field2());
            }
        }
    
        @Override
        public void onFailure(Call<List<PojoItem>> call, Throwable t) {
            Log.e(TAG, t.getMessage());
        }
    });
    

I hope that would be helpful to someone

Good luck,'.

Maher Abuthraa
  • 17,493
  • 11
  • 81
  • 103