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

retrofit send array of objects android
retrofit post body parameters example
how to pass array in retrofit android
how to post json array in android using retrofit
how to pass json array in retrofit android
parse array of json objects retrofit

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.

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;
    }
}

json, parameters that can be an ARRAY or OBJECT in Retrofit on Android where the API I'm parsing returns an OBJECT for an ARRAY of size 1. If you click on Retrofit Android Array then data from JSON Array will be shown and if you click Retrofit Android Object then data from JSON Object will be shown. What’s Next? With the knowledge of how to use Retrofit 2.0 in Android Applications , you can experiment with different API’s available on the internet, parse them and create

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();

Passing Array of Objects as FormUrlEncoded Fields · Issue #490 , I need to talk to an API that accepts an array of images in the following format: I couldn't manage to get Retrofit to generate ordered params. your FieldMap (​assuming images is a List<> and your param map is called params). Your can just pass your complex object and it will take of converting to json. The use of RxJava has really shielded us away from topics like this because RxJava is now a ubiquitous term with android development, so there is almost no reason to adapt your retrofit responses since we rely on Rx observables for most apps.

You can register a TypeAdapter with Gson which conditionally handles this behavior.

You'd call peekToken() first. If it was BEGIN_ARRAY then just deserialize as List<Foo>. If it's BEGIN_OBJECT then deserialize as Foo and wrap in Collections.singletonList.

Now you always have a list whether it's a single item or many.

JSON “object arrays” parsing using Retrofit, RecycleView and , Names of the fields should be the same as in JSON object name. And create Setters and Getters, and contructor with parameters. In 0 step we see that we  In Retrofit, we can configure which converter is used for the data serialization. The answer is that we just pass a List of model class and Retrofit convert JSON Array object to the corresponding Model class. Here is our JSON Array response get from the Server after calling a request API.

Retrofit, Don't forget, we have more tutorials from our Retrofit series for you: Retrofit; Requests; Responses; Converters; Error Handling; Logging; Calladapters Objects can be specified for use as HTTP request body by using the @Body annotation. All modern Android apps need to do network requests. This tutorial is going to cover about how to post with Retrofit 2, a type-safe HTTP client for Android and Java applications.We’re going to get though some popular use cases and examples where Retrofit 2 can be used such as how to post a JSON string, how to post an object, how to post a multipart form, encoded form and so on.

How to handle dynamic JSON response with Retrofit, Imagine you're in a situation where your backend can return a JSON response that's dynamic in nature Read the dynamic parameters object. I'm not really sure of the semantics of how you would create a complex nested object like that. It would have to be form-encoded data inside form-encoded data which isn't something you see often (I've never seen it in use).

Retrofit Android Tutorial : Example of Retrofit 2.0 capturing JSON , Here It will be used to manage calls like GET, POST etc. from JSON Array will be shown and if you click Retrofit Android Object then data from  Form Encoded Requests Using an Array of Values. The example above describes the usage of the @Field annotation only for a single value. If you don’t use string as the object type, Retrofit will do the job to parse a string value from the given object. However, you can pass an array of values using the same key for your form encoded requests

Comments
  • Thanks for your help pakerfeldt!
  • How to use it? @pakerfeldt
  • Thanks pakerfeldt. How would you do this using a TypeAdapter (which is recommended over JsonDeserializer by the GSON authors)?
  • New applications should prefer TypeAdapter, whose streaming API is more efficient than this interface's tree API. Ref: google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/…
  • Haven't done that, but JavaDoc should help you: google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/… Like siva said, do peek() on the JsonReader to check if it's an object or an array.
  • The JsonReader has a pretty good example: google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/… Looks like you have to consume/deserialize the whole object using the TypeAdapter
  • Thanks for the starting points w/ TypeAdapter. Do you know if it's possible to call a default GSON TypeAdapter from within the custom one that I create? The JSON response has many nested objects and I'm trying to avoid manually deserializing all of them for each custom TypeAdapter I create. Also to avoid duplication of code.