Retrofit, top level json object changes name - android

I am using Retrofit to make an api call. The top level object is named depending on the query parameters of the request. For example, a request like this:
api.somewebsite.com/1.0/mix_info?mix_id=69
returns a response like this:
{"69":{
"mix_id":"69",
"mix_title":"A Title",
"mix_file":"https:example.com/mp3",
"mix_genres":"House,Pop",
"mix_dj_id":"57",
"number_votes":"390",
"station":"1"
}
}
Heres a screenie as well that kinda shows the format:
http://prnt.sc/axltcd
Basically, depending on the number passed into the url mix_id= query becomes the name of the top level json object returned in the response.
I was able to hack together something that kinda works:
I have a model Response class which has a member variable that is the top level object, and I use Retrofit's #SerializedName("69") to explicity set the name. This, of course, will only work for a request with the id of 69, otherwise the response returns null object.
It looks like this:
public class Response {
#SerializedName("69")
private _69 _69;
}
Anyway, I'm looking for a way to properly handle these oddly formatted responses. How would this be done?

You can receive your response as JsonElemen, convert it to JsonObject and get element by global variable which you was using for send request.
Response:
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
JsonElement jsonElement = response.body();
JsonObject jObj= jsonElement.getAsJsonObject();
JsonObject number= jObj.get(CONSTANT_WHICH_YOU_USED_FOR_REQEST).getAsJsonObject();
}
Feel free to ask me if any.

Related

Response Code 400 Bad Request when use Json String param in Query tag Retrofit 2 Android

I'm using Retrofit 2 to call API in Android App. I have a API, using POST, which have a String param in Query Tag. I do everything like doc suppose and I test this API successfully in Test Page. I can run another API correctly so the problem is not the way I use Retrofit 2.
Here is my interface:
#POST("/users/{userId}/get_list_friends")
Call<GetListFriendDataResponse> getListFriend(#Path("userId") int userId, #Query("list") String list, #Query("page") int page, #Query("size") int size, #Header("hash") String hash);
Here is my implementation:
ArrayList<String> id = new ArrayList<>();
id.add("4782947293");
JSONArray jsonArray = new JSONArray(id);
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("list", jsonArray);
} catch (JSONException e) {
e.printStackTrace();
}
String list = jsonObject.toString();
Log.e(TAG, "list: " + list);
apiInterface.getListFriend(21, list, 1,1,"AHHIGHTJGI").enqueue(new Callback<GetListFriendDataResponse>() {
#Override
public void onResponse(Call<GetListFriendDataResponse> call, Response<GetListFriendDataResponse> response) {
Log.e(TAG, " response code: "+ response.code());
}
#Override
public void onFailure(Call<GetListFriendDataResponse> call, Throwable t) {
}
});
I always get response code: 400 when use this API.
I'm focusing the "list" var. "list" is a JSON text but I wonder if method "jSon.toString()" is right to get a String from a JSONObject, which can using in Retrofit 2. List param form is:{"list":["12332"]} .
Please help me!
Questions
1) Why you are creating JSONObject and JSONArray on your own?
2) You are creating string using whatever you are creating json.
Eg: {list:["123","456"]}
you are trying pass whole json, I think, instead you need to pass just the array of string to the list key.
Request Sending
{
list:["123","456"]
}
suppose the above json is the request you want to send to server
Now, create model class goto http://jsonschema2pojo.org and paste your json and select json and gson at right side and click on preview.
It will show the classes to map you json to model. Use this model class to set the list to the key in your json
I found my problem. The JSON text contains some special character so I need to convert them to URL encode.
Correct request URL like this:
http://54.169.215.161:8080/users/29/add_friend?list=%7B%22list%22%3A%5B%2215536%22%5D%7D&platform=google
By using Retrofit 2, it uses the URL:
http://54.169.215.161:8080/users/29/add_friend?list={%22list%22:[%2215536%22]}&platform=google
So I get Bad Request Response code.
Retrofit 2 also provides method to convert char sequence to URL encode but it 's not enough. So I don't use Retrofit 's convert method by using this code: encode= true.
so my interface is:
#POST("/users/{userId}/get_list_friends")
Call<GetListFriendDataResponse> getListFriend(#Path("userId") int userId, #Query(value = "list",encoded = true) String list, #Query("page") int page, #Query("size") int size, #Header("hash") String hash);
And I manually convert JSON text to URL encode by code:
list = list.replace("{", "%7B");
list=list.replace("]", "%5D");
list=list.replace("[", "%5B");
list=list.replace(":", "%3A");
list=list.replace("}","%7D");
list = list.replace("\"", "%22");
That's all. Now I can get data by using API.
Suggestion: If u have the same problem, check the URL retrofit return in response and compare to correct URL to see special character, which is not converted to URL encode.

Retrofit2 Specify root element and convert datatype

I've just started working with Retrofit2 and the API I'm consuming wraps all valid responses in a "response" object as shown below. I need to tell Retrofit to parse only the values within response without actually nesting them inside another object. For the login code, I'm also faced with the issue of getting a String which I want to convert to an actual time stamp.
This is a sample response from a login request:
{
"status":"success",
"response":{
"token":"test_token",
"expires":"1485217863"
}
}
In the above the only two actual values are:
token
expires
I'm hoping to end up with something like what is shown below.
public class Token {
#SerializedName("token")
String token;
#SerializedName("expires")
Timestamp expires;
public User(String token, String expires ) {
this.token
this.expires = //conversion code omitted.
}
}
You have a couple of options here. You can either use a custom serialiser/deserialiser, type adapters, or you can simply use pojos and unwrap the result yourself.
Let me start with the easiest solution I can think of. Picture you have these classes:
public class ResponseData<T> {
#SerializedName("status")
#Expose
String status;
#SerializedName("response")
#Expose
T response;
public T getResponse() {
return response;
}
// getters and setters and friends
}
public class Token {
#SerializedName("token")
#Expose
String token;
#SerializedName("expires")
#Expose
Timestamp expires;
public Token(String token, String expires) {
this.token = token;
this.expires = expires;
}
}
So one first thing to notice is the use of #Expose. This is a nice to have, but not extremely necessary. It helps you out when you have custom serialisers.
I assumed that you can have multiple api endpoints that return the same kind of body, where the json is:
{
"status":"success",
"response":{
// Can be anything
}
}
And as you can see the response can be anything.
You can then make your retrofit calls return ResponseData<Token> and in your callbacks you can check the value of status and see if you can do getResponse to unpack the result. The advantage of this approach is that you can reuse ResponseData fairly easily.
Another approach is to use custom serialisers or type adapters. This is in my opinion more laborious, but still a valid approach. I think the answer here is quite extensive and explains how you can do this to get the nested object inside response.
To prepare retrofit to use the type adapters, you'll need to inject a configured Gson instance into it. Here's how:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Token.class, new YourTypeAdapter())
.create();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
// ....
As you can see, we pass the created gson with your type adapter to the GsonConverterFactory used by retrofit. This prepares retrofit to serialise and deserialise Token objects using the given type adapter.
I think the main disadvantage with this approach is that if you want to write a generic deserialiser/serialiser/typeadapter it can become complicated quite fast (assuming you won't have only a Token object).

Unable to get json string as response in Retrofit 2.0 android

This is not a duplicate question.
I am using Retrofit 2.0 and json for network tasks.
Also I am not using GSON to parse json instead I am using simple JsonObject and JsonArray to get model objects from json string.
Firstly guide me which retrofit converter must be used for above scenario.
Secondly, I am not able to get json string as response string.
I tried two approaches -
Approach 1 - I used Call< Void >. In this case the response.body() returns null though status code is 200.
Approach 2 - I used Call< ResponseBody >. In this case call.enqueue methods call 'on failure method' instead of 'onSuccess' and also the response body is null.
The status code is 200 in this case also.
Please suggest how to get the json string as response from retrofit 2.0.
you need to use JsonObject instead of Void or ResponseBody. Your code should be
Call<JsonObject> getCall = request.getDataCall();
getCall.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
}
});
Note : make sure you are using com.google.gson.JsonObject

Android Retrofit Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $

I am using okhttp Retrofit in my Android App to make network requests. On one of the requests I get this error:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
I see a 201 response in the logs but Retrofit throws this error. Below is my code.
signup(signupParams, new Callback<Member>() {
#Override
public void success(Member member, Response response) {
if (member != null) {
UserAccount userAccount = new UserAccount(member);
userAccount.save();
}
#Override
public void failure(RetrofitError re) {
BusProvider.post(new SignupFailedEvent(re, email));
}
});
signupParams value is --
{"emailAddress":"test#gmail.com","password":"tester123","userSource":"APH"}
I have tested this json with jsonLint and it is a valid json. And this is my Member Class which should be the response ideally.
public class Member {
public String emailAddress;
public String token;
public long id;
public String firstName;
public String lastName;
}
Example of the response should be something like this:
{
"emailAddress": "test#gmail.com",
"id": 1437811,
"token": "sdhshdghsdhhsdbcjhbsjdhc",
"firstName": "John",
"lastName": "Smith"
}
if you sure the postman work, and the model same as the JSON parameter,
maybe you use the "accept-encoding: gzip" or like that in your request header.
retrofit doesn't work with gzip, remove that from the header.
If the source code that you posted for the Member class is accurate, then you are not getting the response JSON that you think you are.
The error message means that the JSON parser found a String where it expected a complex object.
Since you don't have any complex objects in the Member class, the result probably just isn't valid JSON (it doesn't start with an opening curly bracket).
Try either turning on verbose logging in Retrofit as suggested in one of the comments, or posting the same data to the API using a tool like "Postman", and see what the result actually is.
This is cause .your respons is not json formated . it may include with string or expected } . to identify this .you have to check with postman and change view type in body as HTML . there you can see full response and you can validate with *https://jsonlint.com/ . else in the case of Dynamic json . you can use JsonElement as response.

Retrofit: Get original response body when request fails

I am using Retrofit (1.6.1) with Robospice (1.4.14) to get data from some services (response should be in JSON).
In some cases, I might receive a HTML error page instead of a JSON response. The server returns a 200 status code, and I can not change that. In such cases, RoboSpice will call the onRequestFailure(SpiceException) method.
There, I am able to get the original RetrofitError excpetion, but the body is null. This is how I get it:
if (spiceException.getCause() instanceof RetrofitError) {
RetrofitError error = (RetrofitError) spiceException.getCause();
error.getBody(); // returns null
}
After investigating the source code of Retrofit, I found out that the body is replaced with null if the conversion fails (which is the case here, as Retrofit expect JSON and receives HTML).
The following line in RestAdapter is the source of my issue:
response = Utils.replaceResponseBody(response, null);
Is there a way to not set the body to null? In an other SO question, I found that if the server returns 4xx, the body is kept, but I can not change that.
You should probably create a retrofitted method that will just return a retrofit.client.Response and manually call conversion on it if the response body is in the necessary format.
Your Retrofit interface:
...
#GET("/foo/bar")
Response fooBarMethod(Object foo, Object bar);
...
Your RoboSpice request:
...
#Override
public final FooBar loadDataFromNetwork() throws Exception {
Response r = getService().fooBarMethod(foo, bar);
if (isBodyInHtmlFormat()) {
// cool stuff
throw new ResponseIsHtmlException();
} else {
// it is wise to make sure that it is
// exactly the same converter you are passing to
// your RetrofitSpiceService
Converter converter = createGsonConverter();
return (FooBar) converter.fromBody(response.getBody(), FooBar.class);
}
}

Categories

Resources