How to Handle Parameters That Can Be an Array or Object in Retrofit on Android

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



Leave a reply



Submit