How can Retrofit 2.0 parse nested JSON object? - android

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;

Related

How to parse nested json using retrofit in expandable Listview? [duplicate]

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;

How to write a Pojo using a dynamic key inside JSONarray

I don't even know if this is a valid question but I am having a hard time converting the API result to POJO since some key are dynamic.
{
"data": [{
"something_edit": true
},
{
"test_null": false
}
],
"success": true
}
As you can see the key inside data are dynamic. I tried using jsonschema2pojo or other converter but it is declaring a named variable which is not a good result. BTW I am using retrofit and GSON library
EDIT:
So here is the flow, so the keys are the ones I asked on the API. So for Example I asked something_edit1, something_edit2 and something_edit3. The data result will be.
{
"data": [{
"something_edit1": true
}, {
"something_edit2": false
},
{
"something_edit3": false
}
],
"success": true
}
You can use Json Object or Generics for your condition.
Using Json Object you can check, if key is exist in your json.
if(yourJsonObject.hasOwnProperty('key_name')){
// do your work here
}
Using Generic you have to check, if your Pojo have instance of the
Pojo.
if(YourMainPOJO instanceOf YourChildPojo){
// do your work here
}
Try to view only Generic part in this link.
It's hard to determine or you have to declare all the possible fields in your POJO or write your own json parser extending the Gson Parser or use a JsonElement which can be converted into json array, object and primitive, based on that result you can convert back to some specific pojo.
/**
* this will convert the whole json into map which you can use to determine the json elements
*
* #param json
*/
private void getModelFromJson(JsonObject json) {
Gson gson = new Gson();
Map<String, JsonElement> jsonElementMap = gson.fromJson(json.toString(), new TypeToken<Map<String, JsonElement>>() {
}.getType());
for (Map.Entry<String, JsonElement> jsonElementEntry : jsonElementMap.entrySet()) {
if (jsonElementEntry.getValue().isJsonPrimitive()) {
//json primitives are data types, do something
//get json boolean
//you can here also check if json element has some json object or json array or primitives based on that
//you can convert this to something else after comparison
if (true) {
InterestModelResponse response = gson.fromJson(jsonElementEntry.getValue().getAsJsonObject().toString(), InterestModelResponse.class);
//use your dynamic converted model
}
} else {
//do something else
}
}
}
2 Years ago we did a project in which we had to handle notifications data with different type of objects in same array we handle that while using retrofit
this is our retrofit Creator class
class Creator {
public static FullTeamService newFullTeamService() {
final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
final Retrofit retrofit = new Retrofit.Builder()
.baseUrl(FullTeamService.HOST)
.client(client)
.addConverterFactory(GsonConverterFactory.create(GsonUtils.get()))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit.create(FullTeamService.class);
}
}
and GsonUtils.java is:
public class GsonUtils {
private static final Gson sGson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.registerTypeAdapter(NotificationObject.class, new NotificationDeserializer())
.create();
private GsonUtils() {}
public static Gson get() {
return sGson;
}
}
NotificationObject is something like:
public class NotificationObject {
#SerializedName("ID")
#Expose
private long ID;
#SerializedName("type")
#Expose
private Type type;
#SerializedName("DataObject")
#Expose
private NotificationDataObject dataObject;
public void setDataObject(NotificationDataObject newsFields) {
dataObject = newsFields;
}
#SuppressWarnings("unchecked")
public <T> T getDataObject() {
return (T) dataObject;
}
public enum Type {
#SerializedName("0")
CHAT_MESSAGE,
#SerializedName("10")
GAME_APPLICATION,
#SerializedName("20")
GAME_APPLICATION_RESPONSE,
#SerializedName("30")
GAME_INVITE....
}
}
NotificationDataObject as new class is like:
public class NotificationDataObject {}
and finally NotificationDeserializer is like:
public class NotificationDeserializer implements JsonDeserializer<NotificationObject> {
#Override
public NotificationObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final JsonObject itemBean = json.getAsJsonObject();
final NotificationObject object = GsonUtils.getSimpleGson().fromJson(itemBean, NotificationObject.class);
switch (object.getType()) {
case CHAT_MESSAGE:
break;
case GAME_APPLICATION:
object.setDataObject(GsonUtils.get().fromJson(itemBean.get("DataObject").getAsJsonObject(),
GameApplicationNotification.class));
break;
case GAME_APPLICATION_RESPONSE:
object.setDataObject(GsonUtils.get().fromJson(itemBean.get("DataObject").getAsJsonObject(),
GameApplicationResponseNotification.class));
break;
case GAME_INVITE:
object.setDataObject(GsonUtils.get().fromJson(itemBean.get("DataObject").getAsJsonObject(),
GameInviteNotification.class));
break;
}
return object;
}
}
Happy coding ...!
any query will be appreciated...

Retrofit 2: Handling dynamic JSON response

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

Expected BEGIN_OBJECT but was BEGIN_ARRAY : Retrofit2 Android

I am new in JSON parsing and trying to parse the following JSON:
[
{
"id" : 1,
"title" : {
"rendered": "a link"
},
"categories": [ 4,9,11 ],
"links":{
"featuredmedia":[
{
"href": link
}
]
}
},
...
]
My Interface is:
public interface MediaAPI {
#GET("Media")
Call<LinkList> getDetails();
}
My model classes are:
public class LinkList {
private List<Links> links;
// getter and setter
}
...
public class Links {
private List<Featuredmedia> Featured = new ArrayList<Featuredmedia>();
// getter and setter
}
...
public class Featuredmedia {
private String href;
// getter and setter
}
and Client code is:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ROOT_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
MediaAPI service = retrofit.create(MediaAPI.class);
Call<LinkList> call = service.getDetails();
call.enqueue(new Callback<LinkList>() {
#Override
public void onResponse(Call<LinkList> call, Response<LinkList> response) {
if(response.isSuccessful()){
successToast();
}
else {
failToast();
}
}
#Override
public void onFailure(Call<LinkList> call, Throwable t) {
Log.d("Failed", t.getMessage());
showToast();
}
});
I only need to get the link inside "featuredmedia" so I only included those in the models. I also got some idea about the error from here but the error still there.
Any suggestion how to solve this will be great help.
Change the Links model like this .
public class Links {
private List<Featuredmedia> featuredmedia = new ArrayList<Featuredmedia>();
// getter and setter
}
And also LinkList should be like this .
public class LinkList {
private Links links;
// getter and setter
}
Gson will take care of Deserializing the json to your LinksList and expects an object for this to work.
Your model basically expects something like :
{
links: [...]
}
Since your Response Json is an array, you want to have a model representing this: any Array or List will do.
So instead of using LinkList, just use e.g. Call<List<Links>>

JacksonConverter and ObjectMapper for nested objects

I am using Retrofit and JacksonConverter to fetch JSON data from an API.
The JSON response is like this:-
[
{
"id": 163,
"name": "Some name",
"nested": {
"id": 5,
"name": "Nested Name",
}
}
]
But in my class I only want the id, name of the parent object and the name of the nested object
I have this class :-
#JsonIgnoreProperties(ignoreUnknown = true)
class A{
#JsonProperty("id")
int mId;
#JsonProperty("name")
String mName;
#JsonProperty("nested/name")
String mNestedName;
}
I don't want to create another object for the nested object inside A. I just want to store the name field of the nested object in A.
The above class doesn't throw any exceptions but the mNestedName will be empty.
Is there anyway to do get the data like this? Do I need to change the #JsonProperty for mNestedName.
This is how I have declared my Retrofit instance
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(JacksonConverterFactory.create())
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
return retrofit;
And I am getting the data through this:-
#GET("something/list/")
Call<List<A>> getA(#Header("Authorization") String authorization);
I don't have experience on Retrofit but if your problem is mostly related to Jackson. You can avoid generating POJO if you want by refactoring your A class with setter/getter and changing the setter param to Generic Object.
#JsonIgnoreProperties(ignoreUnknown = true)
public static class A{
#JsonProperty("id")
int mId;
#JsonProperty("name")
String mName;
#JsonIgnoreProperties
String mNestedName;
public String getmNestedName() {
return mNestedName;
}
#JsonProperty("nested")
public void setmNestedName(Object mNestedName) {
if(mNestedName instanceof Map) {
this.mNestedName = (String)((Map)mNestedName).get("name");
}
}

Categories

Resources