I am using retrofit 2 as a REST client and I cannot figure out how to deserialise a dynamic JSON response.
Depending on the status value (success or failure), our JSON can have two different objects in the result field:
A successful response returns a User object:
{
"status": 200,
"message": "OK",
"result": {
"id": "1",
"email": "bla#bla.bla"
...
}
}
A failed response returns an Error object:
{
"status": 100,
"message": "FAILED",
"result": {
"error": "a user with this account email address already exists"
}
}
I have created 3 POJO classes...
APIResponse:
public class APIResponse<T> {
#Expose private int status;
#Expose private String message;
#Expose private T result;
...
}
User:
public class User {
#Expose private String id;
#Expose private String email;
...
}
Error:
public class Error {
#Expose private String error;
...
}
Here is how I make the call:
#FormUrlEncoded
#PUT(LOGIN)
Call<APIResponse<User>> login(#Field("email") String email, #Field("password") String password);
And here is how I get a response:
#Override
public void onResponse(Call<APIResponse<User>> call, Response<APIResponse<User>> response) {
...
}
#Override
public void onFailure(Call<APIResponse<User>> call, Throwable t) {
...
}
Question:
The API call expects a return type of Call<APIReponse<User>>. However, it might not get a User object back... So how do I modify this approach to accept either APIResponse<User> or APIResponse<Error>.
In other words, how do I deserialise JSON data that can be in two different formats?
Solutions I have looked at:
Including 'error' field in User class or extending Error class (ugly).
Custom interceptor or converter (struggled to understand).
Convince API devs to change it and make my life easier :)
I am not sure if the solution is viable, but you can try changing <APIResponse<User>> to <APIResponse<Object>>
Call<APIResponse<Object>> login(#Field("email") String email, #Field("password") String password);
#Override
public void onResponse(Call<APIResponse<Object>> call, Response<APIResponse<Object>> response) {
//depending on the response status, you can cast the object to appropriate class
Error e = (Error)response.body().getResult();
//or
User u = (User)response.body().getResult();
}
or another alternative, use String instead of POJO
Call<String> login(#Field("email") String email, #Field("password") String password);
retrieve the JSON and serialize manually or use GSON
You should check the response code and use the desired class to process the json
Related
Our team decide to use Retrofit 2.0 and I'm doing some initial research on this library. As stated in the title, I want parse some nested JSON objects via Retrofit 2.0 in our Android app.
For example, here is a nested JSON object with the format:
{
"title": "Recent Uploads tagged android",
"link": "https://www.flickr.com/photos/tags/android/",
"description": "",
"modified": "2015-10-05T05:30:01Z",
"generator": "https://www.flickr.com/",
"items": [
{
"title": ...
"link": ...
"media": {"m":"This is the value I want to get:)"}
"description": ...
"published": ...
"author": ...
"author_id": ...
"tags": ...
},
{...},
...
]
}
I'm interested in the JSON objects inside items array. I notice there are some posts about parsing nested JSON objects via Retrofit 1.X, but the latest Retrofit 2.0 APIs has changed a lot, which is confusing when adapting them to the new APIs.
Two possible solutions come into my mind:
Write my own JSON converter factory which extends Converter.Factory.
Return the raw response in a String type and parse it by myself. But it's not easy to get the raw response from Retrofit 2.0 according to my initial research. Retrofit 2.0 seems to insist in converting the response to something before pass it to me and Retrofit doesn't provide its own StringConverter. (I might be wrong~)
Update: We can actually get the raw response by setting JSONElement as the pojo for the HTTP API interface and use GSONConverter provided by Retrofit as the converter.
Assuming your complete JSON looks like
{
"title": "Recent Uploads tagged android",
"link": "https://www.flickr.com/photos/tags/android/",
"description": "",
"modified": "2015-10-05T05:30:01Z",
"generator": "https://www.flickr.com/",
"items": [
{
"member1": "memeber value",
"member2": "member value"
},
{
"member1": "memeber value",
"member2": "member value"
}
]
}
So Pojo classes would be
public class MainPojo {
private String title;
private String description;
private String link;
private String generator;
private String modified;
private ArrayList<Items> items;
// Getters setters
}
public class Items {
private String member2;
private String member1;
// Getters setters
}
Note : This is similar solution for your JSON. Members of Items.java can be changed if JSON has other keys.
Update for Pojo as new JSON
public class Items {
private String tags;
private String author;
private String title;
private String description;
private String link;
private String author_id;
private String published;
private Media media;
// Getters and Setters
}
public class Media {
private String m;
// Getters and Setters
}
Following code will help to get nested json object and array
for example: json
{
"similar_product":[
{ .....
}
],
"options":{
"Blouse Length":[
{ "value_id":"696556",
}
first we need to create model class, model class items names are same in json item we can use #SerializedName("for exact json name")
public class Product {
public Options options;
public void setOptions(Options options) {
this.options = options;
}
public Options getOptions() {
return options;
}
// length...
public class Options
{
#SerializedName("Blouse Length")
private ArrayList<BlouseLength> blouseLengths;
public void setBlouseLengths(ArrayList<BlouseLength> blouseLengths) {
this.blouseLengths = blouseLengths;
}
public ArrayList<BlouseLength> getBlouseLengths() {
return blouseLengths;
}
}
public class BlouseLength {
String value_id;
public void setValue_id(String value_id) {
this.value_id = value_id;
}
public String getValue_id() {
return value_id;
}
}
}
create Interface for retrofit to get json item in url
// don't need to put values of id in retrofit
ex:: "/api-mobile_.php?method=getProductById&pid="
just pass url parameter in query it automatically fetch the url
for example:
public interface Retrofit_Api {
#FormUrlEncoded
#GET("/api-mobile_.php?method=getProductById")
Call<Product> responseproduct(#Query("pid") String pid);
}
In your Main class
String pid=editid.getText().toString();
final Retrofit adapter = new Retrofit.Builder()
.baseUrl(Product_url)
.addConverterFactory(GsonConverterFactory.create())
.build();
//Creating an object of our api interface
Retrofit_Api api = adapter.create(Retrofit_Api.class);
Call<Product> call = api.responseproduct(pid);
call.enqueue(new Callback<Product>() {
#Override
public void onResponse(Call<Product> call, Response<Product> response) {
ArrayList<Product.BlouseLength> p= new ArrayList(response.body().getOptions().getBlouseLengths());
Editadapter editadapter=new Editadapter(MainActivity.this,p);
recyclerView.setAdapter(editadapter);
}
#Override
public void onFailure(Call<Product> call, Throwable t) {
Log.d("Error", t.getMessage());
}
});
}
Use Gson easy parsing for your models https://github.com/google/gson
My Helper Methods :
public String toJson(Object object) {
return gson.toJson(object);
}
public <T> T fromJson(String json, Class<T> classOfT) {
return gson.fromJson(json, classOfT);
}
public <T> T fromJson(JsonElement jsonElement, Class<T> classOfT) {
return gson.fromJson(jsonElement, classOfT);
}
have you tried volley? ...i prefer it over retrofit now that is a google product.I have working example and if u dont mind i can show you.
http://www.androidhive.info/2014/09/android-json-parsing-using-volley/
I forgot add #SerializedName and #Expose annotations for inner Class objects and after add these annotations problem solved. Like this:
JSON:
{"Id": 1,}
and Class member:
#SerializedName("Id")
#Expose
private int id;
The server responses of all my APIs are in the following format :
{
"code":200,
"status":"success",
"response": {/*different type of responses in all APIs*/}
}
{
"code":200,
"status":"success",
"response": [/*different type of responses in all APIs*/]
}
I have created a class for the response :
public class Response {
#SerializedName("code")
int code;
#SerializedName("status")
String status;
#SerializedName("response")
String response;
}
How can I get different kind of responses in the response variable?
Use generic for your response message:
public class Response<T> {
#SerializedName("code")
int code;
#SerializedName("status")
String status;
#SerializedName("response")
T response;
}
Your response with corresponding data model like this:
Response<List<DataModel>>, Response<DataModel>, etc...
Convert with Gson:
Type type = new TypeToken<Response<T>>(){}.getType();
Response<T> response = new Gson().fromJson(json, type);
I just want to send back an attribute that was not defined in POJO class.
My POJO class is this
public class PostData {
#SerializedName("Token")
#Expose
private String token;
#SerializedName("Amount")
#Expose
private Integer amount;
#SerializedName("AuthID")
#Expose
private String authID;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amt) {
this.amount = amt;
}
public String getAuthID(){return authID;}
public void setAuthID(String authID){
this.authID = authID;
}
}
Amount,Token,AuthID are sent from the client side to API.Here I used HTTP Cloud function as an HTTP POST.I need to send back the TransID to client side which is not there in POJO class.In client side I need to read the response and store it.
Here is my cloud function code:
exports.Add = functions.https.onRequest((req,res)=>{
//var testPublicKey = [xxxxxxxxxxxxxxxxx]
var stripe = require("stripe")("stripe_key");
console.log("Token:"+req.body.Token)
console.log("Amount:"+req.body.Amount)
var tokenToCharge = req.body.Token;
const amountToCharge = req.body.Amount;
var authID = req.body.AuthID;
const databaseRef = admin.firestore();
const payer = databaseRef.collection('deyaPayUsers').doc(authID).collection('Wallet').doc(authID);
const balance = payer.Usd
stripe.charges.create({
amount : amountToCharge,
currency : "usd",
description : "charge created",
source : tokenToCharge,
}, function(err, charge) {
if(charge.status === "succeeded"){
var trans = databaseRef.runTransaction(t=>{
return t.get(payer)
.then(doc=>{
var updatedBalance = doc.data().Usd + parseInt(charge.amount);
t.update(payer,{Usd : updatedBalance});
});//return close
}).then(result=>{
console.log('Transaction success!');
}).catch(err=>{
console.log('Transaction Failure:',err);
});
}
});
});//end of stripe create charge
you add one field in pojo class using transient key is ignore a value to send api call and get client side value.
transient
private String TransID;
and make getter and setter method.
is there any reason why you do not want to create a variable for the TransID in your POJO class? I understand that your POJO class interacts with your Client Side of your application. Hence, having a variable for the TransID(with getters and Setters) will give you more flexibility to read the response and store it as you want.
To read response using Retrofit have a separate class(which is here PostData) that represents the outputs of the JSON response which we get when a request was served by the backend.create an interface class and with #Method(GET, POST, DELETE etc;) annotation and pass parameters to the method.In client-side pass the parameters to the method and get the response by calling the getmethod of the parameter you are expecting.
In my application I want POST some data, and this data get users and POST to server. For server requests I use Retrofit2.
For POST this data I should POST with json format, such as this :
{
"email": "example#example.com",
"username": "example",
"password": "123",
}
After POST data I should check with this results for submit data has Ok ro Not.
{
"status": 200,
"Message": "",
"data": true
}
I give Email, Username and Password with EditText from users, but how can I POST this data to server with Json format?
Please help me, I am amateur and I really need this help
Firstly, create a class for your request, for example, LoginRequest.java
public class LoginRequest {
private String email;
private String username;
private String password;
//getters and setters
}
Secondly, create a class for your response, LoginResponse.java
public class LoginResponse {
private Integer status;
private String Message;
private Boolean data;
//getters and setters
}
Finally, in your interface add this method:
public interface MiApiInterface {
#POST("yourResourceName") Call<LoginResponse> login(#Body LoginRequest request);
}
I hope It could help you, just ask me if you have more question.
have you realised that the return of the login method is a Call, it is for a async call, you could use it like this on your activity:
firstly, create a retrofit instance
Retrofit retrofit = ....
Secondly, create your interface instance like this:
MiApiInterface apiInterface = retrofit.create(MiApiInterface.class);
Finally, you could access the login method:
LoginRequest request = new LoginRequest();
request.set();
....
Call<LoginResponse> responseCall = apiInterface.login(request);
responseCall.enqueue(new Callback<LoginResponse>() {
public void onResponse(...){
LoginResponse loginResponse = response.body();
}
public void onFailure(...){
}
}
To Convert Objects to Json automatically, you should add a Converter Factory on your retrofit builder:
Gson gson = new GsonBuilder().create();
Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
...
dont forget import the Gson library on your gradle.
Here is a tutorial on Retrofit 2: http://www.vogella.com/tutorials/Retrofit/article.html
Alternatively, you can use Volley, it is a library specificaly designed to make http requests on Android. https://developer.android.com/training/volley/index.html
Our team decide to use Retrofit 2.0 and I'm doing some initial research on this library. As stated in the title, I want parse some nested JSON objects via Retrofit 2.0 in our Android app.
For example, here is a nested JSON object with the format:
{
"title": "Recent Uploads tagged android",
"link": "https://www.flickr.com/photos/tags/android/",
"description": "",
"modified": "2015-10-05T05:30:01Z",
"generator": "https://www.flickr.com/",
"items": [
{
"title": ...
"link": ...
"media": {"m":"This is the value I want to get:)"}
"description": ...
"published": ...
"author": ...
"author_id": ...
"tags": ...
},
{...},
...
]
}
I'm interested in the JSON objects inside items array. I notice there are some posts about parsing nested JSON objects via Retrofit 1.X, but the latest Retrofit 2.0 APIs has changed a lot, which is confusing when adapting them to the new APIs.
Two possible solutions come into my mind:
Write my own JSON converter factory which extends Converter.Factory.
Return the raw response in a String type and parse it by myself. But it's not easy to get the raw response from Retrofit 2.0 according to my initial research. Retrofit 2.0 seems to insist in converting the response to something before pass it to me and Retrofit doesn't provide its own StringConverter. (I might be wrong~)
Update: We can actually get the raw response by setting JSONElement as the pojo for the HTTP API interface and use GSONConverter provided by Retrofit as the converter.
Assuming your complete JSON looks like
{
"title": "Recent Uploads tagged android",
"link": "https://www.flickr.com/photos/tags/android/",
"description": "",
"modified": "2015-10-05T05:30:01Z",
"generator": "https://www.flickr.com/",
"items": [
{
"member1": "memeber value",
"member2": "member value"
},
{
"member1": "memeber value",
"member2": "member value"
}
]
}
So Pojo classes would be
public class MainPojo {
private String title;
private String description;
private String link;
private String generator;
private String modified;
private ArrayList<Items> items;
// Getters setters
}
public class Items {
private String member2;
private String member1;
// Getters setters
}
Note : This is similar solution for your JSON. Members of Items.java can be changed if JSON has other keys.
Update for Pojo as new JSON
public class Items {
private String tags;
private String author;
private String title;
private String description;
private String link;
private String author_id;
private String published;
private Media media;
// Getters and Setters
}
public class Media {
private String m;
// Getters and Setters
}
Following code will help to get nested json object and array
for example: json
{
"similar_product":[
{ .....
}
],
"options":{
"Blouse Length":[
{ "value_id":"696556",
}
first we need to create model class, model class items names are same in json item we can use #SerializedName("for exact json name")
public class Product {
public Options options;
public void setOptions(Options options) {
this.options = options;
}
public Options getOptions() {
return options;
}
// length...
public class Options
{
#SerializedName("Blouse Length")
private ArrayList<BlouseLength> blouseLengths;
public void setBlouseLengths(ArrayList<BlouseLength> blouseLengths) {
this.blouseLengths = blouseLengths;
}
public ArrayList<BlouseLength> getBlouseLengths() {
return blouseLengths;
}
}
public class BlouseLength {
String value_id;
public void setValue_id(String value_id) {
this.value_id = value_id;
}
public String getValue_id() {
return value_id;
}
}
}
create Interface for retrofit to get json item in url
// don't need to put values of id in retrofit
ex:: "/api-mobile_.php?method=getProductById&pid="
just pass url parameter in query it automatically fetch the url
for example:
public interface Retrofit_Api {
#FormUrlEncoded
#GET("/api-mobile_.php?method=getProductById")
Call<Product> responseproduct(#Query("pid") String pid);
}
In your Main class
String pid=editid.getText().toString();
final Retrofit adapter = new Retrofit.Builder()
.baseUrl(Product_url)
.addConverterFactory(GsonConverterFactory.create())
.build();
//Creating an object of our api interface
Retrofit_Api api = adapter.create(Retrofit_Api.class);
Call<Product> call = api.responseproduct(pid);
call.enqueue(new Callback<Product>() {
#Override
public void onResponse(Call<Product> call, Response<Product> response) {
ArrayList<Product.BlouseLength> p= new ArrayList(response.body().getOptions().getBlouseLengths());
Editadapter editadapter=new Editadapter(MainActivity.this,p);
recyclerView.setAdapter(editadapter);
}
#Override
public void onFailure(Call<Product> call, Throwable t) {
Log.d("Error", t.getMessage());
}
});
}
Use Gson easy parsing for your models https://github.com/google/gson
My Helper Methods :
public String toJson(Object object) {
return gson.toJson(object);
}
public <T> T fromJson(String json, Class<T> classOfT) {
return gson.fromJson(json, classOfT);
}
public <T> T fromJson(JsonElement jsonElement, Class<T> classOfT) {
return gson.fromJson(jsonElement, classOfT);
}
have you tried volley? ...i prefer it over retrofit now that is a google product.I have working example and if u dont mind i can show you.
http://www.androidhive.info/2014/09/android-json-parsing-using-volley/
I forgot add #SerializedName and #Expose annotations for inner Class objects and after add these annotations problem solved. Like this:
JSON:
{"Id": 1,}
and Class member:
#SerializedName("Id")
#Expose
private int id;