Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize Query-Parameter in Retrofit

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?

like image 496
Frame91 Avatar asked Feb 13 '15 22:02

Frame91


2 Answers

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();
like image 81
Rolf W. Avatar answered Nov 03 '22 16:11

Rolf W.


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;
  }
}
like image 39
William Avatar answered Nov 03 '22 16:11

William