Imagine the following request:
@POST("/recipes/create")
void createRecipe(@Query("recipe") Recipe recipe, Callback<String> callback);
I would like to have toJson(recipe) but unfortunately my request is just calling toString() for my recipe which does not work at all.
I could override the toString inside of Recipe but I'd rather have a general solution.
I cannot use @Body as I need to specify, what I'm sending (i need to have "recipe=json(theRecipe)".
I also cannot change the serialization to add "recipe=" as I'm not in charge of the server.
At the moment I'm using a QueryMap Map where I put in a serialized object. Although this works, it's not a very nice solution in my opinion.
Can I somehow intercept the retrofit-adapter?
This is now possible when registering a custom Converter.Factory
that overrides the stringConverter
method, which is called when resolving parameters. The Github issue that @William referred to 2 years ago doesn't seem to be updated since support was added.
Method Javadoc:
Returns a Converter for converting type to a String, or null if type cannot be handled by this factory. This is used to create converters for types specified by @Field, @FieldMap values, @Header, @HeaderMap, @Path, @Query, and @QueryMap values.
The example below delegates to Gson, but in the same way any type of conversion can be applied to the parameters.
Example: GsonStringConverterFactory
class GsonStringConverterFactory
extends Converter.Factory {
private final transient Gson gson;
GsonStringConverterFactory(final Gson gson) {
this.gson = gson;
}
@Override
public Converter<?, String> stringConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
final TypeAdapter typeAdapter;
typeAdapter = gson.getAdapter(TypeToken.get(type));
return new StringConverter<>(typeAdapter);
}
private static class StringConverter<T>
implements Converter<T, String> {
private final TypeAdapter<T> typeAdapter;
private StringConverter(final TypeAdapter<T> typeAdapter) {
this.typeAdapter = typeAdapter;
}
@Override
public String convert(final T value)
throws IOException {
/* This works in our case because parameters in this REST api are always some kind of scalar
* and the toJson method converts these to simple json types. */
final String jsonValue;
jsonValue = typeAdapter.toJson(value));
if (jsonValue.startsWith("\"") && jsonValue.endsWith("\"") {
/* Strip enclosing quotes for json String types */
return jsonValue.substring(1, jsonValue.length() - 1);
} else {
return jsonValue;
}
}
}
}
Registering the converter:
To register the custom converter, your Retrofit builder could look something like this:
new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(new GsonStringConverterFactory(gson))
.build();
I don't think it supports this right now in some nice way. Check this answer by one of the authors: https://github.com/square/retrofit/issues/291
The suggested method from that answer is to create a custom type that overrides the toString()
method, because Retrofit internally uses String.valueOf(value)
to convert query or path parameters to strings.
So, you could have something like this:
class Recipe {
public int id;
public String title;
@Override
public String toString() {
// You can use a GSON serialization here if you want
// This is not a robust implementation
return Integer.toString(id) + "-" + title;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With