How to handle parameters that can be an ARRAY or OBJECT in Retrofit on Android?
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;
}
}
ARRAY or OBJECT in Retrofit on Android using TypeAdapter, in a two depth level
When you implement a custom type adapter, make sure that your type adapter has balanced token reading and writing: if you open a composite token pair like [
and ]
, you have to close it (applies for both JsonWriter
and JsonReader
). You just don't need this line to fix your issue:
jsonReader.beginObject();
because it moves the JsonReader
instance to the next token, so the next token after BEGIN_OBJECT
is either NAME
or END_OBJECT
(the former in your case sure).
Alternative option #1
I would suggest also not to use ad-hoc Gson
object instatiation -- this won't share the configuration between Gson
instances (say, your "global" Gson
has a lot of custom adapters registered, but this internal does not have any thus your (de)serialization results might be very unexpected). In order to overcome this, just use TypeAdapterFactory
that is more context-aware than a "free" Gson
instance.
final class PaginationTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory paginationTypeAdapterFactory = new PaginationTypeAdapterFactory();
private PaginationTypeAdapterFactory() {
}
static TypeAdapterFactory getPaginationTypeAdapterFactory() {
return paginationTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Classes can be compared using == and !=
if ( typeToken.getRawType() != Pagination.class ) {
// Not Pagination? Let Gson pick up the next best-match
return null;
}
// Here we get the references for two types adapters:
// - this is what Gson.fromJson does under the hood
// - we save some time for the further (de)serialization
// - you classes should not ask more than they require
final TypeAdapter<Links> linksTypeAdapter = gson.getAdapter(Links.class);
final TypeAdapter<Links[]> linksArrayTypeAdapter = gson.getAdapter(Links[].class);
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) new PaginationTypeAdapter(linksTypeAdapter, linksArrayTypeAdapter);
return typeAdapter;
}
private static final class PaginationTypeAdapter
extends TypeAdapter<Pagination> {
private final TypeAdapter<Links> linksTypeAdapter;
private final TypeAdapter<Links[]> linksArrayTypeAdapter;
private PaginationTypeAdapter(final TypeAdapter<Links> linksTypeAdapter, final TypeAdapter<Links[]> linksArrayTypeAdapter) {
this.linksTypeAdapter = linksTypeAdapter;
this.linksArrayTypeAdapter = linksArrayTypeAdapter;
}
@Override
public void write(final JsonWriter out, final Pagination pagination)
throws IOException {
linksTypeAdapter.write(out, pagination.links);
}
@Override
public Pagination read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
// Switches are somewhat better: you can let your IDE or static analyzer to check if you covered ALL the cases
switch ( token ) {
case BEGIN_ARRAY:
return new Pagination(linksArrayTypeAdapter.read(in));
case BEGIN_OBJECT:
return new Pagination(linksTypeAdapter.read(in));
case END_ARRAY:
case END_OBJECT:
case NAME:
case STRING:
case NUMBER:
case BOOLEAN:
case NULL:
case END_DOCUMENT:
// MalformedJsonException, not sure, might be better, because it's an IOException and the read method throws IOException
throw new MalformedJsonException("Unexpected token: " + token + " at " + in);
default:
// Maybe some day Gson adds something more here... Let be prepared
throw new AssertionError(token);
}
}
}
}
Alternative option #2
You can annotate your private Links links;
with @JsonAdapter
and bind a type adapter factory directly to links: Gson
will "inject" links
objects directly to Pagination
instances, so you don't even need a constructor there.
How can I send an object array in a retrofit POST?
First create a callback interface like this and pass the whole Object class.
@POST(URL)
public void newObject(@Body YourObject object, Callback<Boolean> success);
Retrofit uses Gson to serialize and deserialize JSON by default. For Example, If your Object class looked like this:
public class YourObject {
@Expose
private String param1;
@Expose
private String param2;
/**
*
* @return
* The param1
*/
public String getParam1() {
return param1;
}
/**
*
* @param param1
* The param1
*/
public void setParam1(String param1) {
this.param1 = param1;
}
/**
*
* @return
* The param2
*/
public String getParam2() {
return param2;
}
/**
*
* @param param2
* The param2
*/
public void setParam2(String param2) {
this.param2 = param2;
}
}
Then Gson would automatically serialize into the following JSON,
[
{
"param1": "string1",
"param2": "string2"
},
{
"param1": "string3",
"param2": "string4"
}
]
And you are all done!
Add an array as request parameter with Retrofit 2
I have finally founded a solution by using Arrays.toString(int []) method and by removing spaces in this result because Arrays.toString return "[0, 1, 3, 5]". And my request method looks like this
@GET("http://server/service")
Observable<Void> getSomething(@Query("array") String array);
How can I pass the param and value to post api using retrofit in android?
You can pass json object / json array
using @Body by converting the json model
to POJO
( using GSON
) .
Check this out !
Android Retrofit - Pass list of objects as associative array
What I was looking for is the @FieldMap
annotation. That allows to build a map of name/values to pass as POST parameters.
@FormUrlEncoded
@POST("api/v1/patient/{id}/workout")
fun addPatientWorkout(@Path("id") id: Long,
@Field("title") title: String,
@FieldMap exercises: Map<String,String>)
: Single<Response<Workout>>
And that gets called with the following code:
val exerciseFields: MutableMap<String, String> = mutableMapOf()
workout.exercises.forEachIndexed { index, exercise ->
exerciseFields["exercises[$index][duration]"] = exercise.duration.toString()
exerciseFields["exercises[$index][type]"] =exercise.type.name.toLowerCase()
}
return addPatientWorkout(
workout.patient?.id ?: -1,
workout.title,
exerciseFields)
Related Topics
Android Application Icon Not Showing Up
How to Refresh the Fragment After Activity Finished in Android
Android: How to Scale a Bitmap to Fit the Screen Size Using Canvas/Draw Bitmap
Retrofit Body Giving Null Values But Coming Perfect in Postman
How Capture/Log API Calls (Function Calls) Made by Android App
How to Get the Json Response of a Post Request in a Webview
How to Show Text in Lower Case in Android
How to Install the App Twice Without Interference in Android
How to Change the Default Height of a Bottomsheetdialog
Apk Signing Error:Failed to Read Key from Keystore
How to Clear Navigation Stack After Navigating to Another Fragment in Android
Get Height and Width of a Layout Programmatically
Videoview to Match Parent Height and Keep Aspect Ratio
How to Remove Background Resource of Button
How to Display a Loading Spinner While Getting Data from Firestore
Download a File Programmatically on Android