34

I'm working on an Android project which needs a JSONObject for the body of my POST request. After putting the keys and values of the JSON I got the following line:

{
    "xxxx":"zzzzzzz",
    "yyyy":"uuuuuuu"
}

But the server got the following:

{
    "name_value_pairs": {
                        "xxxx":"zzzzzzz",
                        "yyyy":"uuuuuuu"
    }
}

I've already tried a JSONStringer but it wasn't really helpful because the Content-Type of the request is application/json.

UPDATE

I'm not trying to construct a JSONObject because it's already done by using the following line of code (the same given by @osayilgan):

JSONObject jsonRequest = new JSONObject();
jsonRequest.put("xxxx", "zzzzzzz");
jsonRequest.put("yyyy", "uuuuuuu");

Here is not the problem. The interface described below is used to communicate with the server.

public interface MyService {
    @Headers({"Content-type: application/json",
              "Accept: */*"})
    @POST("/test")
    void testFunction(@Body JSONObject jsonObject, Callback<Response> callback);
}

The server got the request with the second JSON as Body which is disappointing. I note that the key name_value_pairs is automatically added to the object.

Does anybody know how can I fix this?

VMai
  • 10,156
  • 9
  • 25
  • 34
13KZ
  • 1,295
  • 5
  • 24
  • 43
  • Can you post the code you are using to get the two JSON blobs? My off the cuff guess is that you are getting the content of `name_value_pairs`, and not the content of the object it contains. – emerssso Sep 24 '14 at 16:42
  • I update the question. I construct the first one but the key name_value_pairs is automatically added when I passed the JSONObject as body of my request – 13KZ Sep 25 '14 at 07:36
  • I dont want the "name value pairs" key added to my json object. Any idea how to prevent this? – nr5 May 20 '16 at 09:59
  • [look this same problem, register type adapter](https://stackoverflow.com/a/52222707/1528524) – lingyfh Sep 07 '18 at 12:47
  • works perfectly with `RequestBody` like this -> `RequestBody body = RequestBody.create(MediaType.parse("text/plain"), text);` for detailed answer https://futurestud.io/tutorials/retrofit-2-how-to-send-plain-text-request-body – Kidus Tekeste Mar 21 '20 at 08:00

8 Answers8

33

Issue:

Retrofit by default uses GSON to convert HTTP bodies to and from JSON. The object which is specified with @Body annotation will be passed to GSON for serialization, which basically converts the JAVA object to JSON representation. This JSON representation will be the HTTP request body.

JSONObject stores all the key-value mapping in a member variable by name nameValuePairs. Here is an excerpt of JSONObject implementation:

public class JSONObject {
    ...
    private final Map<String, Object> nameValuePairs;
    ...
}

When you pass JSONObject to @Body annotation, this JSONObject is seraliazed, hence the HTTP request body contains : {"nameValuePairs": "actual JSON Object"}.

Solution:

Pass the actual JAVA object to @Body annotation, not it's corresponding JSONObject. GSON will take care of converting it to JSON representation.

For e.g.

class HTTPRequestBody {
   String key1 = "value1";
   String key2 = "value2";
   ...
}

// GSON will serialize it as {"key1": "value1", "key2": "value2"}, 
// which will be become HTTP request body.

public interface MyService {
    @Headers({"Content-type: application/json",
              "Accept: */*"})
    @POST("/test")
    void postJson(@Body HTTPRequestBody body, Callback<Response> callback);
}

// Usage
MyService myService = restAdapter.create(MyService.class);
myService.postJson(new HTTPRequestBody(), callback);

Alternative solution:

If you still want to send raw JSON as HTTP request body, then follow the solution mentioned by Retrofit author here.

One of the suggested solution is to use TypedInput:

public interface MyService {
  @POST("/test")
  void postRawJson(@Body TypedInput body, Callback<Response> callback);
}

String json = jsonRequest.toString();
TypedInput in = new TypedByteArray("application/json", json.getBytes("UTF-8"));
myService.postRawJson(in, callback);
Community
  • 1
  • 1
Manish Mulimani
  • 17,535
  • 2
  • 41
  • 60
  • 8
    Thanks for ur answer. It's really helpful. I found another way to avoid having the key nameValuePairs. Instead of using JSONObject of android, I work with JsonObject of GSON and contruct the object with the method addProperty(String property, _THEVALUE_). – 13KZ Oct 03 '14 at 10:16
13

Use com.google.gson.JsonObject instead of org.json.JSONObject.

JSONObject jsonRequest = new JSONObject();
jsonRequest.put("xxxx", "zzzzzzz");
jsonRequest.put("yyyy", "uuuuuuu");

Change to

JsonObject jsonRequest = new JsonObject();
jsonRequest.addProperty("xxxx", "zzzzzzz");
jsonRequest.addProperty("yyyy", "uuuuuuu");

Then in interface

public interface MyService {
    @Headers({"Content-type: application/json",
              "Accept: */*"})
    @POST("/test")
    void testFunction(@Body JsonObject jsonObject, Callback<Response> callback);
}

JSONObject class keeping the values in LinkedHashMap with the variable name of nameValuePairs, When Gson trying to convert the JSONObject's instance into JSON, GSON keeps the structure(which has the variable nameValuePairs). That causing this problem.

Anu Martin
  • 711
  • 1
  • 9
  • 20
8

you have to covert JSONObject to JsonObject of GSON

follow this way

 JsonParser jsonParser = new JsonParser();
  JsonObject jsonObject = (JsonObject)jsonParser.parse(actualjsonobject.toString());

then pass in body

HashMap<String,Object> body=new HashMap();

 body.put("content",jsonObject);
gaurav gupta
  • 513
  • 1
  • 6
  • 13
1

Thanks to 13KZ, pointed me in the right direction, and to flesh it out here is what I now have to solve this issue.

Definitions

private JsonObject gsonResultTwoWeek;
private JsonObject gsonResultDay;
private JsonObject gsonResult;

Initialise

gsonResult = new JsonObject();
gsonResultDay = new JsonObject();
gsonResultTwoWeek = new JsonObject();

Use

gsonResultDay.addProperty(epoch, value);

where data is a string and value is an int in my case and is in a for loop to add multiple values

And then to pull it all together

gsonResult.addProperty("accounts", 2);
gsonResult.add("todaydata", gsonResultDay);
gsonResult.add("2weekdata", gsonResultTwoWeek);

Finally my interface

public interface ApiInterface {

    @POST("/groupdata")
    void postGroupData(@Body JsonObject body,Callback<StatusResponse> cb);

}

What hits my server is this

{"accounts":2,"todaydata":{"1423814400":89,"1423816200":150,"1423818000":441},"2weekdata":{"1423699200":4869,"1423785600":1011}}
Longmang
  • 1,663
  • 1
  • 13
  • 12
0

My solution is based on 13KZ's

public class MyRequest {

  @SerializedName(Constants.ID)
  private String myID;
  @SerializedName(Constants.PARAM_ANSWERS)
  private JsonObject answers;

     public MyRequest(String id, Hasmap<String, String> answers) {
         this.myID = id;
         this.answers = new JsonObject();
         for (String s: answers.keySet()) {
             this.answers.addProperty(s, answers.get(s));
         }
     }
}
Fran
  • 21
  • 3
0
    JSONObject jsonRequest = new JSONObject();
                    try {
                        jsonRequest.put("abc", "test");
                        jsonRequest.put("cba", "tye");
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }

                    Log.d("jsonobject", "onClick: "+jsonRequest);

    (result: {"abc":"test","cba":"tye"})

    JsonParser jsonParser = new JsonParser();
    JsonObject jsonObject = (JsonObject)jsonParser.parse(jsonRequest.toString());

                Log.d("jsonobjectparse", "onClick: "+jsonObject);

(result: {"abc":"test","cba":"tye"})

Once you are put the values into the jsonObject pass to the jsonParser it will solve the issue

thats all. enjoy your coding.
Mahendren Mahisha
  • 1,072
  • 11
  • 9
0
nameValuePairs in retfrofit error

get this type
{
  "nameValuePairs": {
    "email": "mailto:test1@gmail.com",
    "password": "12345678"
  }
}

need this type 
 {
    "email": "mailto:test1@gmail.com",
    "password": "12345678"
  }


    @POST("login")
    suspend fun getLogin(@Body jsonObject: RequestBody) : Response<LoginModel>
    
    
    
    In Repository Class
      Add this line for conver in json utf-8 fomat
      val body = jsonObject.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
    
    class LoginRepository constructor(private val  retrofitService: RetrofitService) {

    suspend fun getLogin(jsonObject: JSONObject): NetworkState<LoginModel> {
        val body = jsonObject.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())



        val response = retrofitService.getLogin(body)
        return if (response.isSuccessful) {
            val responseBody = response.body()
            if (responseBody != null) {
                NetworkState.Success(responseBody)
            } else {
                NetworkState.Error(response)
            }
        } else {
            NetworkState.Error(response)
        }
    }
}
-1

It appears that you are attempting to transmit the actual JSONObject rather the the JSON text-string representation of the object. A look at the specification for the JSONObject class shows that you should be using the .toString() method to get the JSON text representation of the data structure kept by the JSONObject. Thus, you should be able to change:

public interface MyService {
    @Headers({"Content-type: application/json",
              "Accept: */*"})
    @POST("/test")
    void testFunction(@Body JSONObject jsonObject, Callback<Response> callback);
}

to:

public interface MyService {
    @Headers({"Content-type: application/json",
              "Accept: */*"})
    @POST("/test")
    void testFunction(@Body String jsonObject.toString(), Callback<Response> callback);
}

The only change being JSONObject jsonObject to String jsonObject.toString().

Alternately, you could brute force it by just taking the string that you have have of the JSON and replace '"name_value_pairs": {' with '' and the last '}' in the string with ''. JSON is just a string of text. Other than it being inelegant, there is no reason that you can not manipulate the text. Those two replacements will result in a valid JSON text-object. The whitespace indentation won't look correct to a human, but a machine parsing the JSON string does not care if the whitespace is correct.

Makyen
  • 31,849
  • 12
  • 86
  • 121