Martin Revert Martin Revert - 2 months ago 17
reST (reStructuredText) Question

How to parse this JSON response with GSON after a Retrofit GET request?

I'm trying to parse a JSON response using GSON after a Retrofit GET request. I don't need all the keys and values so I only @Expose the ones that I need and instructed the parser to do so. The request fires OK and the response come clean, but looking into logcat I found this error which evidently points me that POJO model is bad formatted or implemented:

04-09 12:16:01.679 5604-5604/? V/Retrofit error﹕ retrofit.converter.ConversionException: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

This is the JSON response (which returns ok after GET request):

{
objects: [
{
rating: 0.97,
name: "High Line",
ranking: 0,
url: "http://www.thehighline.org",
price: null,
phone: "2125006035",
last_sync: 1428328869,
photos: [
"https://irs3.4sqi.net/img/general/original/11402168_2zKtnTfWXPJJJAaX7N6g1EMPTR7ahNqSAOsMotN-jNU.jpg"
],
local_id: 13778,
likes: 0,
city_id: 2621,
address: "btwn Gansevoort & W 34th St",
resource_uri: "/api/v1/venues/40f1d480f964a5206a0a1fe3/",
id: "40f1d480f964a5206a0a1fe3",
categories: [
{
name: "Park",
parent: {
local_id: 7,
name_id: "sights",
name: "Landmarks",
id: "4d4b7105d754a06377d81259"
},
local_id: 494,
name_id: "park",
category_id: 7,
id: "4bf58dd8d48988d163941735"
}
],
location: {
lat: 40.7470618874989,
lng: -74.0051937103271
}
},
{
rating: 0.97,
name: "Central Park",
ranking: 0,
url: "http://www.centralparknyc.org",
price: null,
phone: "2123106600",
last_sync: 1428521923,
photos: [
"https://irs2.4sqi.net/img/general/original/655018_Zp3vA90Sy4IIDApvfAo5KnDItoV0uEDZeST7bWT-qzk.jpg"
],
local_id: 13826,
likes: 0,
city_id: 2621,
address: "59th St to 110th St",
resource_uri: "/api/v1/venues/412d2800f964a520df0c1fe3/",
id: "412d2800f964a520df0c1fe3",
categories: [
{
name: "Park",
parent: {
local_id: 7,
name_id: "sights",
name: "Landmarks",
id: "4d4b7105d754a06377d81259"
},
local_id: 494,
name_id: "park",
category_id: 7,
id: "4bf58dd8d48988d163941735"
}
],
location: {
lat: 40.7888599444948,
lng: -73.9611625671387
}
}
],
meta: {
total_count: 1344,
next: "/api/v1/venues/?city_id=2621&category=topPicks&offset=2&limit=2&format=json",
limit: 2,
offset: 0
}
}


This is the main activity call to the Retrofit service:

Map<String, String> params = new HashMap<String, String>();
params.put("city_id", "2621");
params.put("offset", "0");
params.put("limit", "2");
params.put("category", "topPicks");
params.put("format", "json");


ApiClient.getApiClient().listVenues(params, new Callback<List<ApiResponse>>() {

@Override
public void success(List<ApiResponse> venues, Response response) {

//consumir venues
Log.v("RETROFIT SUCCESS", response.getBody().toString());

mAdapter = new MainCustomAdapter(venues);
mRecyclerView.setAdapter(mAdapter);

}

@Override
public void failure(RetrofitError retrofitError) {

if (retrofitError.getResponse() != null) {
Log.v("Retrofit error", retrofitError.getCause().toString());
}
//manejar el fallo


}
});


This is the Api client:

public class ApiClient {
private static ApiVenuesInterface apiVenues;

public static ApiVenuesInterface getApiClient() {

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

if (apiVenues == null) {
RestAdapter restAdapter = new RestAdapter.Builder()

.setEndpoint("http://endpoint.com")
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.FULL).setLog(new AndroidLog("RETROFIT"))
.build();

apiVenues = restAdapter.create(ApiVenuesInterface.class);
}

return apiVenues;
}

public interface ApiVenuesInterface {
//llamada asíncrona al querystring de venues
@GET("/api/v1/venues")
void listVenues(@QueryMap Map<String, String> params, Callback<List<ApiResponse>> callback);
}}


And finally this my POJO model (which I believe is the place where the main problem is):

public class ApiResponse {
private List<Object> objects = new ArrayList<Object>();

}
class Object {

@Expose
private String name;
@Expose
private List<String> photos = new ArrayList<String>();
@Expose
private String address;
@Expose
private List<Category> categories = new ArrayList<Category>();

/**
*
* @return
* The name
*/
public String getName() {
return name;
}


/**
*
* @return
* The photos
*/
public List<String> getPhotos() {
return photos;
}

/**
*
* @return
* The address
*/
public String getAddress() {
return address;
}

/**
*
* @return
* The categories
*/
public List<Category> getCategories() {
return categories;
}

class Category {
@Expose
private String name;

/**
*
* @return
* The name
*/
public String getName() {
return name;
}

}}


So, how did I must to model my POJO to parse the data I need? Thanks in advance.

IMPORTANT EDIT: This question is valid for Retrofit 1.x. Be aware that Retrofit 2.x is a little bit different than this because it uses annotations and Call methods.

MH. MH.
Answer

As per earlier comment:

Based on a quick look, your Callback return type isn't of type List<ApiResponse>, but rather just ApiResponse. If you change the following, things should start working:

public interface ApiVenuesInterface {
     //llamada asíncrona al querystring de venues
     @GET("/api/v1/venues")
     void listVenues(@QueryMap Map<String, String> params, Callback<ApiResponse> callback);
}

At the moment, you're telling Gson to convert the response into a list of ApiResponse objects, but in reality it's just a single ApiResponse wrapping a list/array of items. You modelled the POJOs correctly, but not so much the retrofit callback.


[edit] answering your follow up question: you can access the wrapped objects by simply adding a getter to your ApiResponse class:

public List<Object> getObjects() {
    return objects;
}

A small tip: it would be good to come up with a different name for your Object class, since its name is identical to the 'mother' of all objects in Java: java.lang.Object. This is bound to lead to confusion and very prone to importing/referencing errors. Try to come up with something a little more descriptive, for example Venue (as it appears that's what you're dealing with, although I may be mistaken).


[edit2] Gson produces an ApiResponse object, which wraps around a List<Object>. Iterating over that list can be done like any other Java iteration; i.e. using the enhanced for-loop:

for (Object object : getObjects()) {
    // get the name for this object
    String name = object.getName();
    // get the address for this object
    String address = object.getAddress();
    // get all the photo urls for this object
    List<String> photos = object.getPhotos();
    // etc...
}

The reason you're seeing i.e. myjavapackage.Object@3c4a86e1 is because Java doesn't know how to represent your objects as a string for printing. You can change this representation by overriding the following method in your Object class:

@Override public String toString() {
    // return a string representation for this object; i.e. its name:
    return name;
}

I'd still recommend to rename your Object class to something more sensible to avoid confusing it with the built-in java.lang.Object.