How to send object parameter in Retrofit GET request?

Alireza A. Ahmadi picture Alireza A. Ahmadi · Apr 21, 2017 · Viewed 7k times · Source

I have a back-end server that works like this

"api/videos?search_object={"cat_id" :2, "channel_id" : 3, etc}

Basily you can give a search object as input and it will filter the list base on that object. Now I want to use this service with Retrofit with something like this

@GET("videos")
Call<VideoListResponse> listVideos(@Query("search_object") VideoSearchObject videoSearchObject);

But the above code doesn't work, I can first convert VideoSearchModel to JSON string that pass it to retrofit like this

@GET("videos")
Call<VideoListResponse> listVideos(@Query("search_object") String jsonString);

I wonder if there is a better more clear way? Any suggestions will be appreciated.

Answer

Lyubomyr Shaydariv picture Lyubomyr Shaydariv · Apr 21, 2017

Retrofit 2 supports it. All you have to do is implementing a custom converter factory with the stringConverter() method overridden.

Consider the following Retrofit-friendly interface with a custom annotation:

@Target(PARAMETER)
@Retention(RUNTIME)
@interface ToJson {
}
interface IService {

    @GET("api/videos")
    Call<Void> get(
            @ToJson @Query("X") Map<String, Object> request
    );

}

The annotation is used to denote an argument that must be converted to a string.

Mock OkHttpClient to always respond with "HTTP 200 OK" and dump request URLs:

private static final OkHttpClient mockHttpClient = new OkHttpClient.Builder()
        .addInterceptor(chain -> {
            System.out.println(chain.request().url());
            return new Response.Builder()
                    .request(chain.request())
                    .protocol(HTTP_1_0)
                    .code(HTTP_OK)
                    .body(ResponseBody.create(MediaType.parse("application/json"), "OK"))
                    .build();
        })
        .build();
private static final Gson gson = new Gson();
private static final Retrofit retrofit = new Retrofit.Builder()
        .client(mockHttpClient)
        .baseUrl("http://whatever")
        .addConverterFactory(new Converter.Factory() {
            @Override
            public Converter<?, String> stringConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
                if ( !hasToJson(annotations) ) {
                    return super.stringConverter(type, annotations, retrofit);
                }
                return value -> gson.toJson(value, type);
            }

            private boolean hasToJson(final Annotation[] annotations) {
                for ( final Annotation annotation : annotations ) {
                    if ( annotation instanceof ToJson ) {
                        return true;
                    }
                }
                return false;
            }
        })
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();

To test it you can simply invoke the service interface method:

final IService service = retrofit.create(IService.class);
service.get(ImmutableMap.of("k1", "v1", "k2", "v2")).execute();

Result:

http://whatever/api/videos?X={%22k1%22:%22v1%22,%22k2%22:%22v2%22}

Where the X parameter argument is an encoded representation of {"k1":"v1","k2":"v2"}.