Android JSON stream data parsing example using Gson

If the request to the Web service is a simple JSON object, then you can convert the HTTP response to a String object and from there convert it to a Java Object using the Google JSON parser. But sometimes you might come across a huge data set that is being sent from the back-end server and you have to parse that information to either display it or save it to the Sqlite database for later use resulting in Java out of memory heap issues if you take the first approach.

Well in a situation like that the best solution is to implement a stream parser with the help of Gson JsonReader and JsonWriter classes. These classes operate on a JSON document as a sequence of tokens that are traversed in depth-first order. Because the streams operate on one token at a time, they impose minimal memory overhead. Here is example that implements both these classes and displays a list of countries sent to the mobile app from a MySql database using a Java Servlet.
Android JSON stream data parsing example using Gson

Sample JSON data returned from the Java Servlet

{"countryList":[{"code":"ABW","name":"Aruba","continent":"North
America"},{"code":"AFG","name":"Afghanistan","continent":"Asia"},{"code":"AGO","name":"
Angola","continent":"Africa"},{"code":"AIA","name":"Anguilla","continent":"North
America"},{"code":"ALB","name":"Albania","continent":"Europe"},{"code":"AND","name":"
Andorra","continent":"Europe"},{"code":"ANT","name":"Netherlands
Antilles","continent":"North America"},{"code":"ARE","name":"United Arab
Emirates","continent":"Asia"},{"code":"ARG","name":"Argentina","continent":"South
America"},{"code":"ARM","name":"Armenia","continent":"Asia"},{"code":"ASM","name":"
American Samoa","continent":"Oceania"},{"code":"ATA","name":"Antarctica","continent":"Antarctica"},
{"code":"ATF","name":"French Southern territories","continent":"Antarctica"},{"code":"ATG","name":"Antigua and
Barbuda","continent":"North America"},{"code":"AUS","name":"Australia1","continent":"Oceania"},
...
...

Source for Java Servlet using JsonWriter class - CountryJSONData.java

package com.as400samplecode;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import com.as400samplecode.util.Country;
import com.google.gson.Gson;
import com.google.gson.stream.JsonWriter;

public class CountryJSONData extends HttpServlet {
 
 private static final long serialVersionUID = 1L;
 
 public CountryJSONData() {
  super();
 }

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  doPost(request,response);
 }

 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

  PrintWriter out = response.getWriter();
  
  
  Connection conn = null;             
  PreparedStatement stmt = null;      
  String sql = null;
  
  try {       
   Context ctx = (Context) new InitialContext().lookup("java:comp/env");
   conn = ((DataSource) ctx.lookup("jdbc/mysql")).getConnection(); 

   Gson gson = new Gson();
   JsonWriter jsonWriter = new JsonWriter(out);
   jsonWriter.beginObject();
   
   sql = "Select * from country where code <> 'CIV'"; 
   stmt = conn.prepareStatement(sql);
   ResultSet rs = stmt.executeQuery();  
   
   jsonWriter.name("countryList");
   jsonWriter.beginArray();
   while(rs.next()){ 
    
    Country country = new Country();
    country.setCode(rs.getString("code").trim());
    country.setName(rs.getString("name").trim());
    country.setContinent(rs.getString("continent").trim());
    gson.toJson(country, Country.class, jsonWriter);
    
   }                                                                          
   jsonWriter.endArray();
   
   rs.close();                                                                
   stmt.close();                                                              
   stmt = null;                                                               
   
   conn.close();                                                              
   conn = null;  
   
   jsonWriter.name("success").value(true);
   jsonWriter.endObject();
   jsonWriter.close();

  }                                                                
  catch(Exception e){
   e.printStackTrace();
  }                       

  finally {                                                        
   
   if (stmt != null) {                                             
    try {                                                          
     stmt.close();                                                 
    } catch (SQLException sqlex) {                                 
     // ignore -- as we can't do anything about it here            
    }                                                              

    stmt = null;                                             
   }                                                         

   if (conn != null) {                                       
    try {                                                    
     conn.close();                                           
    } catch (SQLException sqlex) {                           
     // ignore -- as we can't do anything about it here      
    }                                                        

    conn = null;                                             
   }                                                         
  }               

 }
 
}

Source for Country Object - Country.java

package com.as400samplecode;

public class Country {
 
 String code = null;
 String name = null;
 String continent = null;
 String region = null;
 Double lifeExpectancy = null;
 Double gnp = null;
 
 public String getCode() {
  return code;
 }
 public void setCode(String code) {
  this.code = code;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getContinent() {
  return continent;
 }
 public void setContinent(String continent) {
  this.continent = continent;
 }
 public String getRegion() {
  return region;
 }
 public void setRegion(String region) {
  this.region = region;
 }
 public Double getLifeExpectancy() {
  return lifeExpectancy;
 }
 public void setLifeExpectancy(Double lifeExpectancy) {
  this.lifeExpectancy = lifeExpectancy;
 }
 public Double getGnp() {
  return gnp;
 }
 public void setGnp(Double gnp) {
  this.gnp = gnp;
 }

 
} 

Layout for the main activity - activity_main.xml

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
 android:layout_height="match_parent" tools:context=".MainActivity">

 <ListView android:id="@+id/countrylistView"
  android:layout_width="match_parent" android:layout_height="wrap_content"
  android:layout_alignParentLeft="true" android:layout_alignParentTop="true" />

</RelativeLayout>

Custom layout for ListView row - country_row.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="6dip" >
 
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Code: "
        android:textAppearance="?android:attr/textAppearanceMedium" />
 
    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView1"
        android:layout_below="@+id/textView1"
        android:text="Name: "
        android:textAppearance="?android:attr/textAppearanceMedium" />
 
    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView2"
        android:layout_below="@+id/textView2"
        android:text="Continent: "
        android:textAppearance="?android:attr/textAppearanceMedium" />
 
    <TextView
        android:id="@+id/continent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/textView3"
        android:layout_alignBottom="@+id/textView3"
        android:layout_toRightOf="@+id/textView3"
        android:text="TextView" />
 
    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/textView3"
        android:layout_toRightOf="@+id/textView3"
        android:text="TextView" />
 
    <TextView
        android:id="@+id/code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/textView2"
        android:layout_alignLeft="@+id/name"
        android:text="TextView" />
 
</RelativeLayout>

Application mainfest - AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.as400samplecode"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="15"
        android:targetSdkVersion="16" />

    <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.as400samplecode.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Source for the main activity using JsonReader class - MainActivity.java

package com.as400samplecode;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

 private static final String LOG_TAG = "JSONStreamReader";
 private ArrayList<Country> countryList = new ArrayList<Country>();
 private boolean success = false;
 private MyCustomAdapter dataAdapter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  //create an ArrayAdaptar from the String Array
  dataAdapter = new MyCustomAdapter(this,
    R.layout.country_row, countryList);
  ListView listView = (ListView) findViewById(R.id.countrylistView);
  // Assign adapter to ListView
  listView.setAdapter(dataAdapter);

  //URI to get the JSON stream data array of countries
  String url = "http://demo.mysamplecode.com/Servlets_JSP/CountryJSONData";
  new MyAsyncTask().execute(url);

 }

 private void displayCountries(){
  //if the request was successful then notify the adapter to display the data
  if(success){
   dataAdapter.notifyDataSetChanged();
  }
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 }

 //custom array adapter to display our custom row layout for the listview
 private class MyCustomAdapter extends ArrayAdapter<Country> {

  public MyCustomAdapter(Context context, int textViewResourceId, 
    ArrayList<Country> countryList) {
   super(context, textViewResourceId, countryList);
  }

  private class ViewHolder {
   TextView code;
   TextView name;
   TextView continent;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {

   ViewHolder holder = null;
   if (convertView == null) {

    LayoutInflater vi = (LayoutInflater)getSystemService(
      Context.LAYOUT_INFLATER_SERVICE);
    convertView = vi.inflate(R.layout.country_row, null);

    holder = new ViewHolder();
    holder.code = (TextView) convertView.findViewById(R.id.code);
    holder.name = (TextView) convertView.findViewById(R.id.name);
    holder.continent = (TextView) convertView.findViewById(R.id.continent);
    convertView.setTag(holder);

   } 
   else {
    holder = (ViewHolder) convertView.getTag();
   }

   Country country = countryList.get(position);
   holder.code.setText(country.getCode());
   holder.name.setText(country.getName());
   holder.continent.setText(country.getContinent());

   return convertView;

  }



 }

 //asynchronous task to get our JSON data with holding up the main thread
 private class MyAsyncTask extends AsyncTask<String, Void, Void> {

  private static final int REGISTRATION_TIMEOUT = 3 * 1000;
  private static final int WAIT_TIMEOUT = 30 * 1000;
  private final HttpClient httpclient = new DefaultHttpClient();

  final HttpParams params = httpclient.getParams();
  private boolean error = false;

  protected Void doInBackground(String... urls) {

   String URL = null;

   try {

    //URL passed to the AsyncTask 
    URL = urls[0];
    HttpConnectionParams.setConnectionTimeout(params, REGISTRATION_TIMEOUT);
    HttpConnectionParams.setSoTimeout(params, WAIT_TIMEOUT);
    ConnManagerParams.setTimeout(params, WAIT_TIMEOUT);


    HttpPost httpPost = new HttpPost(URL);

    //Response from the Http Request
    HttpResponse response = httpclient.execute(httpPost);

    //Check the Http Request for success
    StatusLine statusLine = response.getStatusLine();
    if(statusLine.getStatusCode() == HttpStatus.SC_OK){
     
     Gson gson = new Gson();
     //create a new JSON reader from the response input stream 
     JsonReader jsonReader = new JsonReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
     //begin parsing
     jsonReader.beginObject();
     //stay in loop as long as there are more data elements
     while (jsonReader.hasNext()) {
      //get the element name
      String name = jsonReader.nextName();
      
      if (name.equals("success")) {
       success = jsonReader.nextBoolean();
      }
      //if the element name is the list of countries then start the array
      else if(name.equals("countryList")){
       jsonReader.beginArray();
       while (jsonReader.hasNext()) {
        //parse every element and convert that to a country object
        Country country = gson.fromJson(jsonReader, Country.class);
        //add the country object to the list
        countryList.add(country);
       }
       jsonReader.endArray();
      }
     }
     //end reader and close the stream
     jsonReader.endObject();
     jsonReader.close();

    }
    else{
     //Closes the connection.
     Log.w(LOG_TAG,statusLine.getReasonPhrase());
     response.getEntity().getContent().close();
     throw new IOException(statusLine.getReasonPhrase());
    }


   } catch (Exception e) {
    Log.w(LOG_TAG,e );
    error = true;
    cancel(true);
   }

   return null;

  }

  protected void onCancelled() {
   Log.e(LOG_TAG,"Error occured during data download");
  }

  protected void onPostExecute(Void unused) {
   if (error) {
    Log.e(LOG_TAG,"Data download ended abnormally!");
   } else {
    displayCountries();
   }
  }

 }

}

Reference