How to POST raw whole JSON in the body of a Retrofit request?

前端 未结 23 2382
面向向阳花
面向向阳花 2020-11-22 00:57

This question may have been asked before but no it was not definitively answered. How exactly does one post raw whole JSON inside the body of a Retrofit request?

See

相关标签:
23条回答
  • 2020-11-22 01:33

    The @Body annotation defines a single request body.

    interface Foo {
      @POST("/jayson")
      FooResponse postJson(@Body FooRequest body);
    }
    

    Since Retrofit uses Gson by default, the FooRequest instances will be serialized as JSON as the sole body of the request.

    public class FooRequest {
      final String foo;
      final String bar;
    
      FooRequest(String foo, String bar) {
        this.foo = foo;
        this.bar = bar;
      }
    }
    

    Calling with:

    FooResponse = foo.postJson(new FooRequest("kit", "kat"));
    

    Will yield the following body:

    {"foo":"kit","bar":"kat"}
    

    The Gson docs have much more on how object serialization works.

    Now, if you really really want to send "raw" JSON as the body yourself (but please use Gson for this!) you still can using TypedInput:

    interface Foo {
      @POST("/jayson")
      FooResponse postRawJson(@Body TypedInput body);
    }
    

    TypedInput is a defined as "Binary data with an associated mime type.". There's two ways to easily send raw data with the above declaration:

    1. Use TypedByteArray to send raw bytes and the JSON mime type:

      String json = "{\"foo\":\"kit\",\"bar\":\"kat\"}";
      TypedInput in = new TypedByteArray("application/json", json.getBytes("UTF-8"));
      FooResponse response = foo.postRawJson(in);
      
    2. Subclass TypedString to create a TypedJsonString class:

      public class TypedJsonString extends TypedString {
        public TypedJsonString(String body) {
          super(body);
        }
      
        @Override public String mimeType() {
          return "application/json";
        }
      }
      

      And then use an instance of that class similar to #1.

    0 讨论(0)
  • 2020-11-22 01:35

    If you don't want to create extra classes or use JSONObject you can use a HashMap.

    Retrofit interface:

    @POST("/rest/registration/register")
    fun signUp(@Body params: HashMap<String, String>): Call<ResponseBody>
    

    Call:

    val map = hashMapOf(
        "username" to username,
        "password" to password,
        "firstName" to firstName,
        "surname" to lastName
    )
    
    retrofit.create(TheApi::class.java)
         .signUp(map)
         .enqueue(callback)
    
    0 讨论(0)
  • 2020-11-22 01:37

    Instead of classes we can also directly use the HashMap<String, Object> to send body parameters for example

    interface Foo {
      @POST("/jayson")
      FooResponse postJson(@Body HashMap<String, Object> body);
    }
    
    0 讨论(0)
  • 2020-11-22 01:37

    JSONObject showing error please use

    JsonObject paramObject = new JsonObject(); paramObject.addProperty("loginId", vMobile_Email);

    0 讨论(0)
  • 2020-11-22 01:40

    Yes I know it's late, but somebody would probably benefit from this.

    Using Retrofit2:

    I came across this problem last night migrating from Volley to Retrofit2 (and as OP states, this was built right into Volley with JsonObjectRequest), and although Jake's answer is the correct one for Retrofit1.9, Retrofit2 doesn't have TypedString.

    My case required sending a Map<String,Object> that could contain some null values, converted to a JSONObject (that won't fly with @FieldMap, neither does special chars, some get converted), so following @bnorms hint, and as stated by Square:

    An object can be specified for use as an HTTP request body with the @Body annotation.

    The object will also be converted using a converter specified on the Retrofit instance. If no converter is added, only RequestBody can be used.

    So this is an option using RequestBody and ResponseBody:

    In your interface use @Body with RequestBody

    public interface ServiceApi
    {
        @POST("prefix/user/{login}")
        Call<ResponseBody> login(@Path("login") String postfix, @Body RequestBody params);  
    }
    

    In your calling point create a RequestBody, stating it's MediaType, and using JSONObject to convert your Map to the proper format:

    Map<String, Object> jsonParams = new ArrayMap<>();
    //put something inside the map, could be null
    jsonParams.put("code", some_code);
    
    RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),(new JSONObject(jsonParams)).toString());
    //serviceCaller is the interface initialized with retrofit.create...
    Call<ResponseBody> response = serviceCaller.login("loginpostfix", body);
          
    response.enqueue(new Callback<ResponseBody>()
        {
            @Override
            public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
            {
                try
                {
                 //get your response....
                  Log.d(TAG, "RetroFit2.0 :RetroGetLogin: " + rawResponse.body().string());
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable throwable)
            {
            // other stuff...
            }
        });
    

    Hope this Helps anyone!


    An elegant Kotlin version of the above, to allow abstracting the parameters from the JSON convertion in the rest of your application code:

    interface ServiceApi {
    
        fun login(username: String, password: String) =
                jsonLogin(createJsonRequestBody(
                    "username" to username, "password" to password))
    
        @POST("/api/login")
        fun jsonLogin(@Body params: RequestBody): Deferred<LoginResult>
    
        private fun createJsonRequestBody(vararg params: Pair<String, String>) =
                RequestBody.create(
                    okhttp3.MediaType.parse("application/json; charset=utf-8"), 
                    JSONObject(mapOf(*params)).toString())
    
    }
    
    0 讨论(0)
  • 2020-11-22 01:40

    I found that when you use a compound object as @Body params, it could not work well with the Retrofit's GSONConverter (under the assumption you are using that). You have to use JsonObject and not JSONObject when working with that, it adds NameValueParams without being verbose about it - you can only see that if you add another dependency of logging interceptor, and other shenanigans.

    So what I found the best approach to tackle this is using RequestBody. You turn your object to RequestBody with a simple api call and launch it. In my case I'm converting a map:

       val map = HashMap<String, Any>()
            map["orderType"] = orderType
            map["optionType"] = optionType
            map["baseAmount"] = baseAmount.toString()
            map["openSpotRate"] = openSpotRate.toString()
            map["premiumAmount"] = premiumAmount.toString()
            map["premiumAmountAbc"] = premiumAmountAbc.toString()
            map["conversionSpotRate"] = (premiumAmountAbc / premiumAmount).toString()
            return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JSONObject(map).toString())
    

    and this is the call:

     @POST("openUsvDeal")
    fun openUsvDeal(
            @Body params: RequestBody,
            @Query("timestamp") timeStamp: Long,
            @Query("appid") appid: String = Constants.APP_ID,
    ): Call<JsonObject>
    
    0 讨论(0)
提交回复
热议问题