Restrofit deserializing JSON response - android

I have an auth REST api that returns the following JSON response (I am following http://jsonapi.org convention)
{ "user" : { "id" : 1, "first_name" : "Jack" } }
I noticed that Retrofit/Gson does not parse out the root user object automagically? so instead of giving a User object in my success callback, I am doing the following, was wondering if theres a better way to do this? I am new to Retrofit/Android.
authService.signIn(authJson, new Callback<JsonObject>() {
#Override
public void success(JsonObject jsonObject, Response response) {
User user = new Gson().fromJson(jsonObject.get("user"), User.class);
progress.hide();
signInSuccess(user);
}
#Override
public void failure(RetrofitError retrofitError) {
}
});

I think you should just create a wrapping class like that:
class UserWrapper {
User user;
}
and make that the return type of the Retrofit interface.
There's an issue with your solution as well. If I remember correctly, Retrofit streams the network data directly to Gson on the fly. Your solution, on the other hand, will parse User object after the full response is received, and on the main thread as well.
The penalty may not be that significant for a single request, but it's something you'd probably want to avoid for general optimization.

You can write your own deserializer, register it in GsonBuilder and pass it to the RestAdapter
Registering Type:
GsonBuilder gb = new GsonBuilder();
gb.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
gb.registerTypeAdapter(User.class, new UserDeserializerDeserializer());
OkClient okClient = new OkClient();
Builder restAdapter = new RestAdapter.Builder();
restAdapter.setEndpoint(BASE_URL);
restAdapter.setExecutors(Executors.newCachedThreadPool(), new MainThreadExecutor());
restAdapter.setConverter(new GsonConverter(gb.create()));
mRestAdapter = restAdapter.build();//
UserDeserializer class:
public class UserDeserializer implements JsonDeserializer<User>{
private static Gson sGson = new Gson();
#Override
public User deserialize(JsonElement arg0, Type arg1,
JsonDeserializationContext arg2) throws JsonParseException {
JsonObject userObject = arg0.getAsJsonObject().getAsJsonObject("user");
return sGson.fromJson(userObject, arg1);
}
You are deserializing the response in the success method, which could hang the UI (under assumption you are using MainThreadExecutor for callbacks) in the cases where you have huge huge response.
Approach with writing your own deserializer will parse the response on the background thread too.

Related

Using Retrofit and RxJava, how do I deserialize JSON when it doesn't map directly to a model object?

The API I'm working with returns objects (and their containing objects) in a "flat" format and I'm having trouble getting this to work elegantly with Retrofit and RxJava.
Consider this JSON response for an /employees/{id} endpoint:
{
"id": "123",
"id_to_name": {
"123" : "John Doe"
},
"id_to_age": {
"123" : 30
}
}
Using Retrofit and RxJava, how do I deserialize this to a Employee object with fields for name and age?
Ideally I'd like RxJava's onNext method to be called with an Employee object. Is this possible? Could this perhaps be done with some type of custom deserializer subclass (I'm using Gson at the moment)?
I realize I could create an EmployeeResponse object that maps directly to the JSON response, but having to map the EmployeeResponse to the Employee object every time I use this in an activity seems kind of unfortunate. It also gets much more complicated when the flat response also contains other objects that need to get deserialized and set as fields on the Employee.
Is there a better way?
The complete solution to this will seem like a lot, but this will let you write Retrofit interfaces with Employee instead of EmployeeResponse. Here's the game plan:
You will still need both EmployeeResponse and Employee objects, where EmployeeResponse just maps exactly to what you'd get from the API. Treat the response as a builder for Employee and write a static factory method that returns an Employee from an EmployeeResponse, ie. Employee employee = Employee.newInstance(response);
You will be creating a custom TypeAdapterFactory for Gson. When Gson sees you request a Employee object, we will have the TypeAdapter actually create an EmployeeResponse, then return the Employee via the static factory method described above.
Your TypeAdapterFactory will look something like this:
public class EmployeeAdapterFactory implements TypeAdapterFactory {
#Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return type.getRawType() == Employee.class
? (TypeAdapter<T>) employeeAdapter(gson, (TypeToken<Employee>) type)
: null;
}
private TypeAdapter<Employee> employeeAdapter(Gson gson, TypeToken<Employee> type) {
return new TypeAdapter<Employee>() {
#Override public void write(JsonWriter out, Employee value) throws IOException {
// TODO serialization logic to go from an Employee back to EmployeeResponse structure, if necessary
}
#Override public Employee read(JsonReader in) throws IOException {
return Employee.newInstance(gson.fromJson(in, EmployeeResponse.class));
}
};
}
}
Register the factory when you make Gson:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new EmployeeAdapterFactory())
.build();
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://foo.bar")
.addConverterFactory(GsonConverterFactory.create(gson))
... // your other Retrofit configs, like RxJava call adapter factory
.build();
And now you can safely define all your Retrofit interfaces with Employee instead of EmployeeResponse.

Call webservice using Retrofit return invalid webservice result

I am using retrofit to retrieve login JSON result from server for that i need to post user name and password. I have tried this code but i get response saying invalid Web Service. but i get correct response using Rest.
My code is something like this,
MainActivity.java
String url = "http://***.***.in/***/******/*********/UserService.php?request=Verify_User_Credential";
//making object of RestAdapter
RestAdapter adapter = new RestAdapter.Builder().setEndpoint(url).build();
//Creating Rest Services
RestInterface restInterface = adapter.create(RestInterface.class);
//Calling method to get login report
restInterface.getLoginReport(username, password, new Callback<Model>()
{
#Override
public void success(final Model model, Response response) {
if (RetailConnectUtils.isSuccess(model.getStatus())) {
runOnUiThread(new Runnable() {
#Override
public void run() {
// retrieve status and message from model and display
}
});
}
});
RestInterface::
#FormUrlEncoded
#POST("/GetData")
void getLoginReport(#Field("username")String uname,#Field("password")String password,Callback<Model> cb);
and POJO class model containing json converted values It contain method getStatus and getMessage...
Here what should i mention in #POST("******").. is that webservice method or default retrofit method?

Android Volley request - send object to Spring boot java web service

I have some custom POJO:
class CustomClass {
int x;
String str;
SecondCustomClass obj; //indicate it's not class of simple types
//etc...
}
I want to send instance of him from Android (Volley library) client to web service running on Spring-boot java application. Currently I know how to send data with URL params and return to client custom object. But I want also to send custom object.
Code in Android (I know that I need to use 3'rd parameter which is now null but I'm struggling get it work):
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET,
"BASE_URL?param1=param",
null,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
CustomClass result = new Gson().fromJson(response.toString(), CustomClass.class);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
}
);
volleyQueue.add(request);
Code in server:
EDIT: The solution to receive pojo is using #RequestBody
#RequestMapping("/webmethod")
public CustomClass webmethod(#RequestParam(value="obj_param") #RequestBody CustomClass obj) {
//work with obj
}
What do I need to put in Android side to get it work?
You have to use the JSON object inside the JSON object.Below is the sample. When you are request the Parameter with only one request.
This the Request JSON
{
"x":10,
"str":"MyName",
"SecondCustomClass":{
"id":10,
"title":"make it eassy"
}
}
This is the post parameter request from Android. Try this way.
For more details please use this link

Android: Realm + Retrofit - Serialize apiresponse

Preface: I'm using Retrofit to handle my API calls and Realm(realm.io) to store the data.
The API im dealing with uses the following structure:
Array Response
{
"response":
[
{
"objectField1":"abc"
"objectField2":"abc"
"objectField3":"abc"
"objectField4":"abc"
},
{
"objectField1":"abc"
"objectField2":"abc"
"objectField3":"abc"
"objectField4":"abc"
}
]
}
Single object response
{
"response":
{
"objectField1":"abc"
"objectField2":"abc"
"objectField3":"abc"
"objectField4":"abc"
}
}
All api responses are contained in a response object either in an array (if result size > 1) or an object (if result size == 1).
I currently have my API call as follows:
#GET("/api/myEndpoint")
void getAllExampleObjects(Callback<MyRealmClass> callback);
How can I serialise the API response (handling both array and single object cases) to place them in my realm?
Christian from Realm here.
If you have a single REST API call that can return both a list and a single object, you will have to do something manually. As colriot points out you will have to write your own GSON deserializer. For ideas how to write one see a very good answer in this SO post: How to handle parameters that can be an ARRAY or OBJECT in Retrofit on Android?
To get the objects into Realm you can use realm.copyToRealm(objects) in the following way:
#GET("/api/myEndpoint")
void getAllExampleObjects(Callback<List<MyRealmClass>> callback);
Callback callback = new Callback() {
#Override
public void success(List<MyRealmClass> objects, Response response) {
realm.beginTransaction();
realm.copyToRealm(objects);
realm.commitTransaction();
}
#Override
public void failure(RetrofitError retrofitError) {
}
};

Custom error handling with Retrofit

The REST Api I'm working with has custom codes and messages which are sent from server depending on the state, I would like to implement a custom Callback<T> that calls the success method only if the status code was 0.
Example SUCCESS Response received from server:
{
"code":"0",
"message":"success",
"data": {
"actual_data":"goes_here",
"need_to_construct_objects","from_data"
}
}
Example of FAILURE Response:
{
"code":"301",
"message":"wrong_password",
"data": {
"actual_data":"will_be_null",
"no_need_to_construct_objects","from_data"
}
}
code and message are returned by all requests, the data contains the actual response values, so I would like to do the following:
Check the code and message and only call success() if code is 0.
Call failure() if request failed or code != 0
Construct custom objects based on the data response and pass them via success()
What is the best way to do this? I searched everywhere and could not find a good solution. The only one I got was to let all custom objects have the code and message fields too and check their values inside success(), but this could cause problems in future in case someone forgets to check the code before proceeding.
You can do that quickly by just making an abstract class that implements Callback, and declare your own abstract success and failure methods. The abstract class will handle Retrofit's standard callback methods, interpret the response and call the abstract methods accordingly.
I think another possible approach to this is to override Retrofit's Client interface to build your own Response object.
If you extend OkClient, it can go like this:
public class CustomClient extends OkClient {
#Override public Response execute(Request request) throws IOException {
Response originalRespone = super.execute(request);
int statusCode = 0;
//TODO: read JSON response here (using GSON or similar, and extract status code and message... etc.)
//Convert the status code to HTTP standard status codes, according to the documentation you have.
if(statusCode == 0) statusCode = 200;
//Reconstruct a Response object
return new Response(originalResponse.getUrl(), statusCode, originalResponse.getReason() /*should probably replace with parsed message*/, originalResponse.getHeaders(), originalResponse.getBody());
}
This may be more work than handling your case in Callback, but I think it can help if at some point the API transitions to RESTful API conventions.
This solution comes with its own problem though, because that means the JSON conversion will run twice. One in your client, and another one by Retrofit. Not sure the correct way to do that at the moment. Probably something around TypedInput and a dummy Converter that passes already converted objects.
Create a custom ResponseBodyConverter like this:
public class CustomResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
CustomResponseBodyConverter(TypeAdapter<T> adapter) {
this.adapter = adapter;
}
#Override
public T convert(ResponseBody value) throws IOException,CustomException {
String json = "";
try {
String body = value.string();
json = new JSONObject(body).getJSONObject("data").toString();
int code = new JSONObject(body).getInt("code");
String message = new JSONObject(body).getString("message");
if(code != 0){
throw new CustomException(message);
}
} catch (JSONException e) {
e.printStackTrace();
}
return adapter.fromJson(json);
}
}
It's a better idea to implement a custom callback. You can an example about it below.
public abstract class DefaultRequestCallback<T> implements Callback<T> {
public abstract void failure(Meta meta);
public abstract void success(T responseBean);
#Override
public void success(T baseResponseBean, Response response) {
// You can check your responsebean's error code and
// convert it to a default error
BaseResponseBean bean = (BaseResponseBean) baseResponseBean;
if (bean == null) {
failure(new Meta(ApplicationConstants.ERROR_RETROFIT, "Unknown Error!"));
} else if (bean.getMeta() != null && bean.getMeta().getCode() != ApplicationConstants.RESULT_SUCCESS) {
failure(bean.getMeta());
} else {
success(baseResponseBean);
}
}
#Override
public void failure(RetrofitError error) {
// Convert default error to your custom error.
Meta meta = new Meta(ApplicationConstants.ERROR_RETROFIT, "Error Unknwon");
failure(meta);
}
}
Give your custom callback to your retrofit service interface method.
void yourMethod(DefaultRequestCallback<YourResponseBean> callback);
Good Luck.
This will at least get you started. You can basically create your own custom callback and then handle the success. Look at what was sent and do what you need to.
public class CustomCallback implements Callback {
#Override
public void success(Object o, Response response) {
//Check for success
//if( Success )
//callback.success(o, response);
//else
//Check for error
//callback.failure(error);
}
}
In your case, you can have a class that maps your json response:
class CustomResponse {
String code;
String message;
Data data;
static class Data {
String actualData;
String needToContructObjects;
String noNeedToContructObjects;
}
}
Then, since you're back to the java objects world, you can have a factory-like object inside your success method callback that creates the desired object based on the returned custom response. If you want to get this response in the failure callback, I'd reconsider using Retrofit, since your API is not following a good Rest design.
Although this is plenty possible, and understanding you might not be involved on the API development, be aware this is not a good API design approach. If you are POSTing a login request to the server, you can understand this request as a request to create a resource (an authenticated user session, for instance). If you don't send the correct parameters (the correct username and password in this specific case), the server should reject the resource creation request and send back a 4-hundred-something (4xx) http status code indicating your request was not correct somehow. Retrofit would understand this 4xx status code and call your failure callback, where you could handle the response appropriately.

Categories

Resources