Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle parameters that can be an ARRAY or OBJECT in Retrofit on Android?

I'm having an issue where the API I'm parsing returns an OBJECT for an ARRAY of size 1.

For example, sometimes the API will respond with:

{
    "monument": [
        {
            "key": 4152,
            "name": "MTS - Corporate Head Office",
            "categories": {},
            "address": {}
        },
        {
            "key": 4151,
            "name": "Canadian Transportation Agency",
            "categories": {},
            "address": {}
        },
        {
            "key": 4153,
            "name": "Bank of Montreal Building",
            "categories": {},
            "address": {}
        }
    ],
}

However, if the monument array has only 1 item it becomes an OBJECT (note the lack of [] brackets) like so:

{
    "monument": {
        "key": 4152,
        "name": "MTS - Corporate Head Office",
        "categories": {},
        "address": {}
    }
}

If I define my models like this, I will get an error when only a single item is returned:

public class Locations {
    public List<Monument> monument;
}

If only a single item is returned I get the following error:

Expected BEGIN_OBJECT but was BEGIN_ARRAY ...

And if I define my model like so:

public class Locations {
    public Monument monument;
}

and the API returns an ARRAY I get the opposite error

Expected BEGIN_ARRAY  but was BEGIN_OBJECT ...

I cannot define multiple items with the same name in my model. How can I handle this case?

Note: I cannot make changes to the API.

like image 275
Ryan R Avatar asked Oct 22 '14 05:10

Ryan R


1 Answers

The trick is to write your own Gson deserializer for your Locations class. This would check whether the monument element is an object or an array. Like so:

public class LocationsDeserializer implements JsonDeserializer<Locations> {

    @Override
    public Locations deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        JsonElement monumentElement = json.getAsJsonObject().get("monument");
        if (monumentElement.isJsonArray()) {
            return new Locations((Monument[]) context.deserialize(monumentElement.getAsJsonArray(), Monument[].class));
        } else if (monumentElement.isJsonObject()) {
            return new Locations((Monument) context.deserialize(monumentElement.getAsJsonObject(), Monument.class));
        } else {
            throw new JsonParseException("Unsupported type of monument element");
        }
    }
}

For the convenience, add a vararg constructor to your Locations class:

public class Locations {
    public List<Monument> monuments;

    public Locations(Monument ... ms) {
        monuments = Arrays.asList(ms);
    }
}

Your Monument class stays the same. Something like:

public class Monument {
    public int key;
    public String name;
    // public Categories categories;
    // public Address address;
}

Finally, create your own Gson object and pass it to the retrofit RestAdapter.

Gson gson = new GsonBuilder().registerTypeAdapter(Locations.class, new LocationsDeserializer()).create();

RestAdapter restAdapter = new RestAdapter.Builder()
            .setEndpoint(baseUrl)
            .setConverter(new GsonConverter(gson))
            .build();
like image 110
pakerfeldt Avatar answered Oct 02 '22 05:10

pakerfeldt