How to POST raw whole JSON in the body of a Retrofit request?
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 similar question here. Or is this answer correct that it must be form url encoded and passed as a field? I really hope not, as the services I am connecting to are just expecting raw JSON in the body of the post. They are not set up to look for a particular field for the JSON data.
I just want to clarify this with the restperts once and for all. One person answered not to use Retrofit. The other was not certain of the syntax. Another thinks yes it can be done but only if its form url-encoded and placed in a field (that's not acceptable in my case). No, I can't re-code all the services for my Android client. And yes, it's very common in major projects to post raw JSON instead of passing over JSON content as field property values. Let's get it right and move on. Can someone point to the documentation or example that shows how this is done? Or provide a valid reason why it can/should not be done.
UPDATE: One thing I can say with 100% certainty. You CAN do this in Google's Volley. It's built right in. Can we do this in Retrofit?
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:
-
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);
-
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.
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 {
@POST("/api/login")
fun jsonLogin(@Body params: RequestBody): Deferred<LoginResult>
}
class ServiceApiUsingClass {
//ServiceApi init
fun login(username: String, password: String) =
serviceApi.jsonLogin(createJsonRequestBody(
"username" to username, "password" to password))
private fun createJsonRequestBody(vararg params: Pair<String, String>) =
RequestBody.create(
okhttp3.MediaType.parse("application/json; charset=utf-8"),
JSONObject(mapOf(*params)).toString())
}
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);
}
In Retrofit2, When you want to send your parameters in raw you must use Scalars.
first add this in your gradle:
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:converter-scalars:2.3.0'
Your Interface
public interface ApiInterface {
String URL_BASE = "http://10.157.102.22/rest/";
@Headers("Content-Type: application/json")
@POST("login")
Call<User> getUser(@Body String body);
}
Activity
public class SampleActivity extends AppCompatActivity implements Callback<User> {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ApiInterface.URL_BASE)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiInterface apiInterface = retrofit.create(ApiInterface.class);
// prepare call in Retrofit 2.0
try {
JSONObject paramObject = new JSONObject();
paramObject.put("email", "[email protected]");
paramObject.put("pass", "4384984938943");
Call<User> userCall = apiInterface.getUser(paramObject.toString());
userCall.enqueue(this);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onResponse(Call<User> call, Response<User> response) {
}
@Override
public void onFailure(Call<User> call, Throwable t) {
}
}