sgarman sgarman - 4 years ago 95
JSON Question

Simple way to strip outer array of responses in gson

I'm working with an api (Phillips Hue) that wraps all of it's json responses in an array with one entry (the content).

Example:

[{
"error": {
"type": 5,
"address": "/",
"description": "invalid/missing parameters in body"
}
}]


I usually write standard POJO's parsed by GSON to handle responses but since the response is not a json object I'm a bit stumped on the best way to deal with this. I didn't really want every object to actually be an array that I have to call .get(0) on.

Example of the POJO if it was a JSON obj and NOT wrapped in an array.

public class DeviceUserResponse {
private DeviceUser success;
private Error error;

public DeviceUser getSuccess() {
return success;
}

public Error getError() {
return error;
}

public static class Error {
private int type;
private String address;
private String description;

public int getType() {
return type;
}

public String getAddress() {
return address;
}

public String getDescription() {
return description;
}

@Override
public String toString() {
return "Type: " + this.type
+ " Address: " + this.address
+ " Description: " + this.description;
}
}
}


What I have to do right now:

ArrayList<DeviceUserResponse> response.get(0).getError();


Is there a way that I can strip this array for every response or am I just going to have to do a .get(0) in my POJO's and just not expose it?

Answer Source

I think you've to go with custom deserialization in order to "strip out" the array.

Here a possible solution.

An adapter for your response POJO:

 public class DeviceUserResponseAdapter extends TypeAdapter<DeviceUserResponse> {

   protected TypeAdapter<DeviceUserResponse> defaultAdapter;

   public DeviceUserResponseAdapter(TypeAdapter<DeviceUserResponse> defaultAdapter) {
    this.defaultAdapter = defaultAdapter;
   }

   @Override
   public void write(JsonWriter out, DeviceUserResponse value) throws IOException {
    defaultAdapter.write(out, value);
   }

   @Override
   public DeviceUserResponse read(JsonReader in) throws IOException {
     in.beginArray();
     assert(in.hasNext());
     DeviceUserResponse response = defaultAdapter.read(in);
     in.endArray();
     return response;
    }
 }

A factory for your adapter:

 public class DeviceUserResponseAdapterFactory implements TypeAdapterFactory {

   @Override
   @SuppressWarnings("unchecked")
   public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
     if (type.getRawType()!=DeviceUserResponse.class) return null;

     TypeAdapter<DeviceUserResponse> defaultAdapter = (TypeAdapter<DeviceUserResponse>) gson.getDelegateAdapter(this, type);
     return (TypeAdapter<T>) new DeviceUserResponseAdapter(defaultAdapter);
   }
 }

Then you've to register and user it:

 DeviceUserResponseAdapterFactory adapterFactory = new DeviceUserResponseAdapterFactory();

 GsonBuilder gsonBuilder = new GsonBuilder();
 Gson gson = gsonBuilder.registerTypeAdapterFactory(adapterFactory).create();
 DeviceUserResponse response = gson.fromJson(json, DeviceUserResponse.class);
 System.out.println(response.getError());

This solution will not work if you have the DeviceUserResponse inside other complex JSON object. I that case the adapter will try to find an array and will terminate with an error.

Another solution is to parse it as array and then in your "communication" layer you get only the first element. This will preserve the GSon deserialization.

In the comment you're asking for a more generic solution, here one: The adapter:

public class ResponseAdapter<T> extends TypeAdapter<T> {

protected TypeAdapter<T> defaultAdapter;

public ResponseAdapter(TypeAdapter<T> defaultAdapter) {
    this.defaultAdapter = defaultAdapter;
}

@Override
public void write(JsonWriter out, T value) throws IOException {
    defaultAdapter.write(out, value);
}

@Override
public T read(JsonReader in) throws IOException {
    in.beginArray();
    assert(in.hasNext());
    T response = defaultAdapter.read(in);
    in.endArray();
    return response;
}
}

The factory:

public class ResponseAdapterFactory implements TypeAdapterFactory {

@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    if ((type.getRawType().getSuperclass() != Response.class)) return null;

    TypeAdapter<T> defaultAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, type);
    return (TypeAdapter<T>) new ResponseAdapter<T>(defaultAdapter);
}
}

Where Response.class is your super class from which all the service responses inherit. The first solution advices are still valid.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download