Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Format JSON Body for Retrofit from single string value without model

Is there a way to turn a single String value (plain text, not json) into a JSON body with an annotation? I do not want to create such a simple model.

Example

@POST("foo/{fooId}/bars")
Observable<Void> postBar(@Path("fooId") String styleId, @Body BarModel bar);

class BarModel {
    public String bar;
}

Will give me what I expect:

{
    "bar" : "hello world"
}

Is there a simple way to do it with an annotation? Something like this:

@POST("foo/{fooId}/bars")
Observable<Void> postBar(@Path("fooId") String styleId, @Body("bar") String bar);
like image 955
Nick Cardoso Avatar asked Jul 06 '16 12:07

Nick Cardoso


3 Answers

Retrofit has a Converter.Factory abstract class that you can use to do custom HTTP representation. You can create a Converter to construct a okhttp.RequestBody if the method has a specific annotation.

The end result will look like:

@POST("/")
Call<Void> postBar(@Body @Root("bar") String foo)

and transform: postBar("Hello World") into { "bar" : "Hello World" }.

Let's get started.

Step 1 - create an annotation for the root key (Root.java)

/**
 * Denotes the root key of a JSON request.
 * <p>
 * Simple Example:
 * <pre><code>
 * &#64;POST("/")
 * Call&lt;ResponseBody&gt; example(
 *     &#64;Root("name") String yourName);
 * </code></pre>
 * Calling with {@code foo.example("Bob")} yields a request body of
 * <code>{name=>"Bob"}</code>.
 * @see JSONConverterFactory
 */
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Root {
  /**
   * The value of the JSON root.
   * Results in {"value" : object}
   */
  String value();
}

Step 2 - define your Converter.Factory that detects the annotation (JSONConverterFactory.java). I'm using Gson for JSON parsing but you can use whatever framework you want.

/**
 * Converts @Root("key") Value to {"key":json value} using the provided Gson converter.
 */
class JSONConverterFactory extends Converter.Factory {
    private final Gson gson;
    private static final MediaType CONTENT_TYPE =
            MediaType.parse("application/json");

    JSONConverterFactory(Gson gson) {
        this.gson = gson;
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(
            Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        for (Annotation annotation : parameterAnnotations) {
            if (annotation instanceof Root) {
                Root rootAnnotation = (Root) annotation;
                return new JSONRootConverter<>(gson, rootAnnotation.value());
            }
        }
        return null;
    }

    private final class JSONRootConverter<T> implements Converter<T, RequestBody> {
        private Gson gson;
        private String rootKey;

        private JSONRootConverter(Gson gson, String rootKey) {
            this.gson = gson;
            this.rootKey = rootKey;
        }

        @Override
        public RequestBody convert(T value) throws IOException {
            JsonElement element = gson.toJsonTree(value);
            JsonObject object = new JsonObject();
            object.add(this.rootKey, element);
            return RequestBody.create(CONTENT_TYPE, this.gson.toJson(object));
        }
    }
}

Step 3 - install the JSONConverterFactory into your Retrofit instance

Gson gson = new GsonBuilder().create(); // Or your customized version
Retrofit.Builder builder = ...;
builder.addConverterFactory(new JSONConverterFactory(gson))

Step 4 - Profit

@POST("/")
Call<Void> postBar(@Body @Root("bar") String foo)

Or for your case:

@POST("foo/{fooId}/bars")
Observable<Void> postBar(@Body @Root("bar") String barValue, @Path("fooId") String styleId);
like image 76
Kevin Avatar answered Sep 22 '22 01:09

Kevin


It does not necessarily create a json body, but your api might be able to work with url encoded things

@FormUrlEncoded
@POST("foo/{fooId}/bars")
Observable<Void> postBar(@Path("fooId") String styleId, @Field("bar") String bar);
like image 29
Bartek Lipinski Avatar answered Sep 21 '22 01:09

Bartek Lipinski


Then better to use Hashmap<String, String>. You can pass Hashmap in body directly, by parsing using Gson. And if you need to use multiple place then you can use your own map extending HashMap, so you will be able to add your values in one line. I am posting mine, might it help you -

public class PostParams extends HashMap<String, String> {
    public static PostParams init() {
        return new PostParams();
    }

    public PostParams add(String param, String value) {
        put(param, value);
        return this;
    }

    public PostParams add(String param, String[] values) {
        put(param, new Gson().toJson(values));
        return this;
    }

    public PostParams add(String param, int[] values) {
        put(param, new Gson().toJson(values));
        return this;
    }

    public PostParams add(String param, int value) {
        put(param, value + "");
        return this;
    }

    public PostParams addPlatform() {
        put("Platform", Constants.ANDROID);
        return this;
    }

    public PostParams add(String param, double value) {
        put(param, new Gson().toJson(value));
        return this;
    }

    @Override
    public String toString() {
        return new Gson().toJson(this);
    }
}

Usage will be like -

String postData = new PostParams().add("bar", "Hello World").toString()

Hope it will help :)

like image 31
Neo Avatar answered Sep 22 '22 01:09

Neo