7

I am trying to de-serialize a list of objects from a JSON response. The JSON array has a key, which is causing issues with using GSON to de-serialize it.

I have about 20 objects similar to this.

public class Device extends Entity {
  String device_id;
  String device_type;
  String device_push_id;
}

For most there is an API method which returns a list of objects. The returned JSON looks like this. Because of other clients, changing the format of the JSON is not a reasonable option at this point.

{
   "devices":[
      {
         "id":"Y3mK5Kvy",
         "device_id":"did_e3be5",
         "device_type":"ios"
      },
      {
         "id":"6ZvpDPvX",
         "device_id":"did_84fdd",
         "device_type":"android"
      }
   ]
}

In order to parse this type of response I'm currently using a mix of org.json methods and Gson.

JSONArray jsonResponse = new JSONObject(response).getJSONArray("devices");

Type deviceListType = new TypeToken<List<Device>>() {}.getType();
ArrayList<Device> devices = gson.fromJson(jsonResponse.toString(), deviceListType);

I'm looking for a cleaner method of doing the deserialization as I'd like to use Retrofit. The answer in Get nested JSON object with GSON using retrofit is close to what I need, but doesn't handle Lists. I've copied the generic version of the answer here:

public class RestDeserializer<T> implements JsonDeserializer<T> {
  private Class<T> mClass;
  private String mKey;

  public RestDeserializer(Class<T> targetClass, String key) {
    mClass = targetClass;
    mKey = key;
  }

  @Override
  public T deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)
    throws JsonParseException {

    JsonElement value = jsonElement.getAsJsonObject().get(mKey);
    if (value != null) {
      return new Gson().fromJson(value, mClass);
    } else {
      return new Gson().fromJson(jsonElement, mClass);
    }
  }
}

My goal is to have this call "just work".

@GET("/api/v1/protected/devices")
public void getDevices(Callback<List<Device>> callback);
Community
  • 1
  • 1
Derek
  • 1,572
  • 1
  • 14
  • 25

1 Answers1

1

Use the below class

public class Devices {

@Expose
private List<Device> devices = new ArrayList<Device>();

/**
* 
* @return
* The devices
*/
public List<Device> getDevices() {
return devices;
}

/**
* 
* @param devices
* The devices
*/
public void setDevices(List<Device> devices) {
this.devices = devices;
}

}

Device class

public class Device extends Entity {
  @Expose
  String id;
   @Expose
  String device_id;
  @Expose
  String device_type;
}

or

public class Device extends Entity {
  @Expose @SerializedName("id")
  String deviceId;
   @Expose @SerializedName("device_id")
  String devicePushId;
  @Expose @SerializedName("device_type")
  String deviceType;
}

update retrofit method to

@GET("/api/v1/protected/devices")
public void getDevices(Callback<Devices> callback);

devices.getDevices() //call inside callback method will give you the list

Also, you wont require the custom deserializer

rahul.ramanujam
  • 5,608
  • 7
  • 34
  • 56
  • I considered this, and indeed it is what I will do if no generic option comes up. However because I have 20+ objects like this I'm hoping to avoid having 20 extra classes just to wrap the lists. – Derek Jun 03 '15 at 15:39
  • try this one instead @GET("/api/v1/protected/devices") public void getDevices(Callback callback callback); I haven't tried this myself , let me know if this works .. You dont have to create the extra object if this works – rahul.ramanujam Jun 03 '15 at 15:47
  • That does not work. An issue is how would Gson know that the json element called "devices" is the correct one to unwrap to find it's List or Device[]. – Derek Jun 03 '15 at 15:55
  • The List should work, did you try that ? Retrofit uses gson internally and takes care of the mapping for you . Without retrofit you would have to write your own deserializer – rahul.ramanujam Jun 03 '15 at 15:56
  • I have tried List. The problem is that gson isn't able to intuit on its own how to unwrap {"devices": [ { ... device ... }, { ... device ...} ] }. The above generic deserializer is a compact way of unwrapping single objects, but there is no class literal in java for parameterized types such as `List`. You have given me the idea of using `Device[]` which does have a class literal. I'll try to work that out now. Thanks! – Derek Jun 03 '15 at 16:05
  • I have updated the device class , the class variable names should match the json id or you can use @serializedname() – rahul.ramanujam Jun 03 '15 at 16:08
  • My apologies for not making sure my minimized example had that correct. I use this gson method to account for the difference. `setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)` I have fully tested deserialization of all classes in their simple states. It is only the presence of the wrapped list which is the problem. – Derek Jun 03 '15 at 16:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/79556/discussion-between-r7v-and-derek). – rahul.ramanujam Jun 03 '15 at 16:11
  • I've ended up with wrapper classes. Not great, but it works well and isn't that intrusive in the code. Thanks. – Derek Jun 29 '15 at 08:36