Dedicated Server
Showing posts with label json. Show all posts
Showing posts with label json. Show all posts

Using custom gson converter to parse dynamic json with free source code

We often come across issue where a json dynamically switches its type based on the response. For example, in normal cases suppose that you have a list of cars returned as an array from your backend, but when the number of cars in the list is one, you are given just an abject instead of array. In such cases, your code should be ready to handle the response no matter whether it is an array or an object.


To handle such cases, Gson provides plenty of customisation options. One such option is registerTypeAdapter. So lets dive deeper to check how its done.

In this tutorial we will be using retrofit to make the network calls and of course gson-converter, so go to your app level build.gradle and add the following as dependencies

implementation 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

please add the latest dependency versions for retrofit, converter-gson


Now check the following json strcutures that we will be parsing

cars_object_json.json
{
"cars" : {
   "car_type" : "Hatchback", 
   "car_models" : {"car_name":"Swift" , "power_window":"yes" }
  }
}

cars_array_json.json
{
"cars" : {
   "car_type" : "Sedan" , 
   "car_models" : [ 
      { 
         "car_name" : "Xcent",
         "power_window" : "no"
      },
      {
         "car_name" : "Ciaz",
         "power_window" : "yes"
      } 
  ]}
}


In cars_object_json.json you can see that car_models is an object because there is only one item inside it whereas in cars_array_json.json  car_models is an array since there are more than one items in it. We have to write a parser which could automatically identify the response format and parse both the cases easily. registerTypeAdapter is exactly what we want here.

Alright, lets see some code now

First of all, there are two type of object models or POJOs that we need to create for the above json >> Cars,CarModels

1) Automatic model class/pojo class generation using http://www.jsonschema2pojo.org/
Specify Target language as Java, Source Type as JSON and annotation style as Gson
  • Now click preview and copy the generated java files
Now copy the contents of the class and add it in android studio project

Now copy the classes Cars,CarModels from http://www.jsonschema2pojo.org/ and add it to your project like this

Cars.java

public class Cars {
    @SerializedName("car_type")
    @Expose
    private String carType;
    @SerializedName("car_models")
    @Expose
    private List<CarModel> cardModels = null;

    public String getCarType() {
        return carType;
    }

    public void setCarType(String carType) {
        this.carType = carType;
    }

    public List<CarModel> getCardModels() {
        return cardModels;
    }

    public void setCardModels(List<CarModel> cardModels) {
        this.cardModels = cardModels;
    }

}


CarModels.java

public class CarModel {
    @SerializedName("car_name")
    @Expose
    private String carName;
    @SerializedName("power_window")
    @Expose
    private String powerWindow;

    public String getCarName() {
        return carName;
    }

    public void setCarName(String carName) {
        this.carName = carName;
    }

    public String getPowerWindow() {
        return powerWindow;
    }

    public void setPowerWindow(String powerWindow) {
        this.powerWindow = powerWindow;
    }
}


Now, create a deserializer like shown below

GsonBuilder b = new GsonBuilder();
        b.registerTypeAdapter(Cars.class, new JsonDeserializer<Cars>() {
            @Override
            public Cars deserialize(JsonElement arg0, Type arg1,
                                     JsonDeserializationContext arg2) throws JsonParseException {
                JsonObject CarsObj = arg0.getAsJsonObject();
                JsonObject innerObj=CarsObj.getAsJsonObject("cars");

                Gson g = new Gson();
                Cars a = g.fromJson(arg0, Cars.class);
                List<CarModel> carModels = null;

                if (innerObj.get("car_models").isJsonArray()) {
                    carModels = g.fromJson(innerObj.get("car_models"),
                            new TypeToken<List<CarModel>>() {
                            }.getType());
                } else {
                    CarModel single = g.fromJson(innerObj.get("car_models"), CarModel.class);
                    carModels = new ArrayList<CarModel>();
                    carModels.add(single);
                }
                a.setCardModels(carModels);
                return a;
            }
        });

In the above deserializer, you can clearly see that we have checked whether car_models is an array or an object and then parsed the response accordingly.

Now go ahead and attach the converter to your retrofit implementation so that the entire code for your MainActivity.java will look like this

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        parseJson();
    }

    private void parseJson() {

        GsonBuilder b = new GsonBuilder();
        b.registerTypeAdapter(Cars.class, new JsonDeserializer<Cars>() {
            @Override
            public Cars deserialize(JsonElement arg0, Type arg1,
                                     JsonDeserializationContext arg2) throws JsonParseException {
                JsonObject CarsObj = arg0.getAsJsonObject();
                JsonObject innerObj=CarsObj.getAsJsonObject("cars");

                Gson g = new Gson();
                Cars a = g.fromJson(arg0, Cars.class);
                List<CarModel> carModels = null;

                if (innerObj.get("car_models").isJsonArray()) {
                    carModels = g.fromJson(innerObj.get("car_models"),
                            new TypeToken<List<CarModel>>() {
                            }.getType());
                } else {
                    CarModel single = g.fromJson(innerObj.get("car_models"), CarModel.class);
                    carModels = new ArrayList<CarModel>();
                    carModels.add(single);
                }
                a.setCardModels(carModels);
                return a;
            }
        });
        
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://navneet7k.github.io/")
                .addConverterFactory(GsonConverterFactory.create(b.create()))
                .build();
        

        RequestInterface request = retrofit.create(RequestInterface.class);
        Call<Cars> call1=request.getJson();
        call1.enqueue(new Callback<Cars>() {
            @Override
            public void onResponse(Call<Cars> call, Response<Cars> response) {
                Toast.makeText(MainActivity.this,"Success!",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<Cars> call, Throwable t) {
                Toast.makeText(MainActivity.this,"Failure",Toast.LENGTH_SHORT).show();
            }

        });
    }

}

And your RequestInterface.java for cars_object_json.json  will be like this

interface RequestInterface {
    @GET("cars_object_json.json")
    Call<Cars> getJson();
}

RequestInterface.java for cars_array_json.json  will be like this


interface RequestInterface {
    @GET("cars_array_json.json")
    Call<Cars> getJson();
}

By this method you will get the correct response for Response<Cars> response in the retrofit callbacks even if your response changes dynamically.

Loading images using picasso library in android

In this tutorial we are about to load image in recyclerview using picasso library as shown in the following gif
images loaded in recyclerview using picasso library

In this tutorial we will be parsing the following json array example into a recyclerview along with the images
You can check the json file here https://navneet7k.github.io/cars_list.json
Update internet permission in AndroidManifest.xml as shown below
Add the dependencies for cardview,design,retrofit,converter-gson and picasso in your app level build.gradle as shown below
Please make sure that you add the dependencies in app level build.gradle itself and not project level build.gradle like depicted below

build.gradle files


Create model class like shown below

CarItem.java
Create recyclerview adapter

DataAdapter.java
Here we have used

 Picasso.get().load(articles.get(i).getImage()).resize(500,500).into(viewHolder.car_img); 

to load the image into car_img using Picasso library. Also I have implemented a filter method in the above adapter class which you can use to integrate a search feature in the above recyclerview - you may check this tutorial to integrate search feature inside recyclerview.

create activity file

MainActivity.java
Since we have to parse a json array example, we have used List<CarItem> as response parameter type in retrofit callback methods as shown above. If we were parsing a json object example, we would have to set CarItem as response parameter type.

RequestInterface.java
Now create the resource files

activity_main.xml
item_layout.xml
colors.xml
strings.xml

Android RecyclerView search using adapter Filter method

The following is an overview of what we are about to learn in this tutorial

RecyclerView SearchView example
In this tutorial, we will be parsing a json array example into  a recyclerview as shown above and then use the filter feature in recyclerview adapter to perform search operations in the recyclerview list. The below json is the one that we are about to parse.





AndroidManifest.xml
app level build.gradle file
First of all, add the following changes in resource files

strings.xml
colors.xml
Now go to your "res" dirctory and right click on it, go to new>>Android Resource Directory and choose resource type as menu. Then click ok to create menu directory inside res folder. Now create another file menu_main.xml inside the menu directory that we just created.

menu_main.xml
Now make the following changes and additions to the layout files

activity_main.xml
The following layout provides the design for each item in a recyclerview

item_layout.xml
Our res folder will have the below directory structure
res folder directory structure

Now let us head over to some java code, first of all go ahead and create your model class like shown below
CarModel.java.java

Now create a recyclerview adapter for your recyclerview like shown below
DataAdapter.java
Now create a retrofit interface to define your network endpoint like shown below
RequestInterface.java
Since we are parsing json array example, we will need to fetch the results as a list of CarModel. So finally create MainActivity.java file with the network calls as shown below
Now the complete directory structure will be like
full directory structure

Parse json array with unkown key using Map in Android

Hi, this is yet another tutorial associated with json parsing in android. Let us see how can we parse a json array for which the key is not known beforehand. This means that the key names of the json array has to be identified and parsed dynamically.

You may take a look at one of my previous post regarding json parsing with unknown key, if you haven't already checked.

In this tutorial, we can make use of Map to parse the json array with dynamic key. We will be using retrofit library to perform the network calls.

First of all, add the following dependecies to your app level build.gradle file :

compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

Let us try to parse a json response which will be like this sometimes

{
  "response": "success",
  "servicecode": "134",
  "forecast": {
    "month": {
      "jan": [
        {
          "id": "1",
          "price": "12",
          "Product": "1086",
          "Qty": "14",
          "date": "2018-10-27 16:08:57"
        },
        {
          "id": "2",
          "price": "19",
          "Product": "1746",
          "Qty": "45",
          "date": "2018-10-27 16:08:57"
        }
      ],
      "april": [
        {
          "id": "3",
          "price": "89",
          "Product": "1986",
          "Qty": "15",
          "date": "2018-10-27 16:08:57"
        },
        {
          "id": "1",
          "price": "12",
          "Product": "1086",
          "Qty": "145",
          "date": "2018-10-27 16:08:57"
        }
      ],
      "jun": [
        {
          "id": "81",
          "price": "132",
          "Product": "17086",
          "Qty": "1445",
          "date": "2018-10-27 16:08:57"
        },
        {
          "id": "11",
          "price": "132",
          "Product": "10786",
          "Qty": "1445",
          "date": "2018-10-27 16:08:57"
        }
      ]
    }
  },
  "message": "Competitor Sales."
}

and it will be changing dynamically like this sometimes

{
  "response": "success",
  "servicecode": "134",
  "forecast": {
    "month": {
      "april": [
        {
          "id": "3",
          "price": "89",
          "Product": "1986",
          "Qty": "15",
          "date": "2018-10-27 16:08:57"
        },
        {
          "id": "1",
          "price": "12",
          "Product": "1086",
          "Qty": "145",
          "date": "2018-10-27 16:08:57"
        }
      ]
    }
  },
  "message": "Competitor Sales."
}

Here you can see that the json array key names jan, jun are missing in second response. So we will have to dynamically parse these key names.

From the above response we can see that :

  • the entire response is enclosed in a json object({}), so we will create a model class(Example.java
  • we can see that there is one more json object inside the outer json object - forecast, so we will create another model class for it(Forcast.java)
  • inside forecast, there is one more json object - month, but it contains some dynamically named json array. So we need to create a Map object that takes in a String and List<MonthModel>>. Please note that the Map object is used to parse the following pattern

"april": [
        {
          "id": "3",
          "price": "89",
          "Product": "1986",
          "Qty": "15",
          "date": "2018-10-27 16:08:57"
        }

where String corresponds to "april" and List<MonthModel>> corresponds to the json array enclosed in [{}]

  • So we need to create one more mode class MonthModel.java
Now generate the model classes as explained :

Example.java

public class Example {
    @SerializedName("response")
    @Expose
    private String response;
    @SerializedName("servicecode")
    @Expose
    private String servicecode;
    @SerializedName("forecast")
    @Expose
    private Forecast forecast;
    @SerializedName("message")
    @Expose
    private String message;

    public String getResponse() {
        return response;
    }

    public void setResponse(String response) {
        this.response = response;
    }

    public String getServicecode() {
        return servicecode;
    }

    public void setServicecode(String servicecode) {
        this.servicecode = servicecode;
    }

    public Forecast getForecast() {
        return forecast;
    }

    public void setForecast(Forecast forecast) {
        this.forecast = forecast;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Forecast.java
public class Forecast {
    @SerializedName("month")
    @Expose
    private Map<String, List<MonthModel>> result;

    public Map<String, List<MonthModel>> getResult() {
        return result;
    }

    public void setResult(Map<String, List<MonthModel>> result) {
        this.result = result;
    }
}

MonthModel.java
public class MonthModel {
    @SerializedName("id")
    @Expose
    private String id;
    @SerializedName("price")
    @Expose
    private String price;
    @SerializedName("Product")
    @Expose
    private String product;
    @SerializedName("Qty")
    @Expose
    private String qty;
    @SerializedName("date")
    @Expose
    private String date;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    public String getProduct() {
        return product;
    }

    public void setProduct(String product) {
        this.product = product;
    }

    public String getQty() {
        return qty;
    }

    public void setQty(String qty) {
        this.qty = qty;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}

Now we can make the retrofit call as shown below
private void getMonthData() {
        Gson gson = new GsonBuilder()
                .setLenient()
                .create();
        Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .baseUrl("enter_base_url")
                .build();
        RequestInterface requestInterface = retrofit.create(RequestInterface.class);
        Call<Example> call = requestInterface.getMonths();
        call.enqueue(new Callback<Example>() {
            @Override
            public void onResponse(Call<Example> call, Response<Example> response) {
                Map<String, List<MonthModel>> resultMap=response.body().getForecast().getResult();
                Toast.makeText(MainActivity.this, "Success", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<Example> call, Throwable t) {
                Toast.makeText(MainActivity.this, "Failure", Toast.LENGTH_SHORT).show();
            }
        });
    }

Create RequestInterface.java like this :
public interface RequestInterface {
    @GET("enter_url_endpoint")
    Call<Example> getMonths();
}

Now inside onResponse, you will have all the dynamic json array as Map inside resultMap

You may also check similar issue in StackOverflow



How to parse json with unknown key in android ?

We might have come across json responses which are having unknown key names. For example, a single response may output different key names when invoked different times. In this case we may not know the key value before hand so that we could parse it using the key name. In such case we should first get all the key values into a list of key names and then iterate over the key names one by one.

For example if a single endpoint(api request) gives two responses during two cases like below

case 1
{
"1":"abc",
"2","abc",
"3":"abc",
"4","abc"
}

case 2
{
"5":"abc",
"6","abc",
"7":"abc",
"8","abc"
}

You could make use of Iterator for looping through the list of key names.

Create a model class for saving the key and value pair like below

KeyValueModel.java

public class KeyValueModel {
    String keyName;
    String valueName;

    public KeyValueModel(String keyName, String valueName) {
        this.keyName = keyName;
        this.valueName = valueName;
    }

    public String getKeyName() {
        return keyName;
    }

    public String getValueName() {
        return valueName;
    }
}

Suppose you have a json response jsonObjectResponse(string). Then parse it like following


JSONObject jsonResponse = new JSONObject(jsonObjectResponse);
Iterator  iteratorObj = jsonResponse.keys();
ArrayList<KeyValueModel> responseList=new ArrayList<KeyValueModel>();
while (iteratorObj.hasNext())
     {
       String keyName = (String)iteratorObj.next();
       String valueName=jsonResponse.getString(keyName);
       KeyValueModel keyValueModel=new KeyValueModel(keyName,valueName);
       responseList.add(keyValueModel);
     }

Now you can access your response from responseList

How to fix "Expected a string but was BEGIN_OBJECT" in GSON

You might have come across a gson exception that says  "Expected a string but was BEGIN_OBJECT". This means that you are parsing the response as if it were a string, but actually it is a json object.

I would recommend you to check how to parse json in android if you have no prior knowledge in parsing json

Add these dependencies in your app's build.gradle file

compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

Suppose your response is a json object, something like the following

{ 
"username":"jon",
"email":"jon@email.com", 
"user_array": [
 { 
 "user_address":"jon",
 "user_location":"jon@email.com"
 }, {..},
 .
 . 
 ]
}

You will receive the error Expected a string but was BEGIN_OBJECT if you try to parse it like this
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("base_url")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

RequestInterface request = retrofit.create(RequestInterface.class);
Call<String> call=request.getJson();
call.enqueue(new Callback<String>() {
    @Override    
    public void onResponse(Call<String> call, Response<String> response) {
        progressDialog.dismiss();
        String s= String.valueOf(response.get("username"));
        JsonArray user_array= response.getAsJsonArray("user_array");
        Toast.makeText(PrintTicket.this,response.toString(),Toast.LENGTH_SHORT).show();
    }

    @Override    
    public void onFailure(Call<String> call, Throwable t) {
        progressDialog.dismiss();
        Toast.makeText(PrintTicket.this,t.toString(),Toast.LENGTH_SHORT).show();
    }
});

GSON will throw the above mentioned error because you have used Call<Stringinstead of using Call<JsonObject> or  using a pojo class

Solution 1 : by using JsonObject:


Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("base_url")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

RequestInterface request = retrofit.create(RequestInterface.class);
Call<JsonObject> call=request.getJson();
call.enqueue(new Callback<JsonObject>() {
    @Override    
    public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
        progressDialog.dismiss();
        String s= String.valueOf(response.get("username"));
        JsonArray user_array= response.getAsJsonArray("user_array");
        Toast.makeText(PrintTicket.this,response.toString(),Toast.LENGTH_SHORT).show();
    }

    @Override    
    public void onFailure(Call<JsonObject> call, Throwable t) {
        progressDialog.dismiss();
        Toast.makeText(PrintTicket.this,t.toString(),Toast.LENGTH_SHORT).show();
    }
});

You can see that here we are expecting a JsonObject as response parameter instead of String. Now we can parse the response correctly as we are expecting the same format mentioned in the response. Also dont forget to change the response param in interface to Call<JsonObject> as shown below

RequestInterface.java
public interface RequestInterface {

@GET("api_endpoint")
Call<JsonObject> getJson();

}


Solution 2 : by using POJO class

First create a pojo class like this

Example.java
public class Example {

@SerializedName("username")
@Expose
private String username;
@SerializedName("email")
@Expose
private String email;
@SerializedName("user_array")
@Expose
private List<UserArray> userArray = null;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public List<UserArray> getUserArray() {
return userArray;
}

public void setUserArray(List<UserArray> userArray) {
this.userArray = userArray;
}

}

Then use it inside your retrofit call like this

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("base_url")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

RequestInterface request = retrofit.create(RequestInterface.class);
Call<Example> call=request.getJson();
call.enqueue(new Callback<Example>() {
    @Override    
    public void onResponse(Call<Example> call, Response<Example> response) {
        progressDialog.dismiss();
        String user_name= response.getUsername();
        user_array= new ArrayList<>(response.getUserArray());
        Toast.makeText(PrintTicket.this,response.toString(),Toast.LENGTH_SHORT).show();
    }

    @Override    
    public void onFailure(Call<Example> call, Throwable t) {
        progressDialog.dismiss();
        Toast.makeText(PrintTicket.this,t.toString(),Toast.LENGTH_SHORT).show();
    }
});

RequestInterface.java
public interface RequestInterface {
@GET("api_endpoint")
Call<Example> getJson();
}

You can use Call<String> as response param only if your response is not enclosed in curly brackets. In this case the response will be a plain text