Ryan R Ryan R - 2 months ago 27
JSON Question

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.

Answer

As a complement to my previous answer, here's a solution using a TypeAdapter.

public class LocationsTypeAdapter extends TypeAdapter<Locations> {

    private Gson gson = new Gson();

    @Override
    public void write(JsonWriter jsonWriter, Locations locations) throws IOException {
        gson.toJson(locations, Locations.class, jsonWriter);
    }

    @Override
    public Locations read(JsonReader jsonReader) throws IOException {
        Locations locations;

        jsonReader.beginObject();
        jsonReader.nextName();       

        if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
            locations = new Locations((Monument[]) gson.fromJson(jsonReader, Monument[].class));
        } else if(jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
            locations = new Locations((Monument) gson.fromJson(jsonReader, Monument.class));
        } else {
            throw new JsonParseException("Unexpected token " + jsonReader.peek());
        }

        jsonReader.endObject();
        return locations;
    }
}
Comments