These days, JSON is considered to be a superior data exchange format to XML due to its lighter weight. As it happens, Android provides several specialized classes for parsing JSON. These have been around ever since API level 1 (the first Android release)! In today’s article, we’ll learn how to use Android’s JSON classes to load a JSON data set into memory and work with its contents.
Step One: Fetching the Data
Although it is possible to load JSON data from a file, in most cases, your app will fetch the data from a feed or other online server, for instance, a weather office or Bugzilla’s REST API.
We’ll be working with the latter for the remainder of this article. Here is the formatted JSON string for bug #35 at “https://bugzilla.mozilla.org/rest/bug/35”:
{ "bugs" : [ { "alias" : "firstBug", "assigned_to" : "mcafee@gmail.com", "assigned_to_detail" : { "email" : "mcafee@gmail.com", "id" : 1672, "name" : "mcafee@gmail.com", "real_name" : "Chris McAfee" }, "blocks" : [], "cc" : [ "hchang@mozilla.com", "rexyrexy2@gmail.com", "tymerkaev@gmail.com", "wlevine@gmail.com" ], "cc_detail" : [ { "email" : "hchang@mozilla.com", "id" : 475800, "name" : "hchang@mozilla.com", "real_name" : "Henry Chang [:henry][:hchang]" }, { "email" : "rexyrexy2@gmail.com", "id" : 463956, "name" : "rexyrexy2@gmail.com", "real_name" : "" }, { "email" : "tymerkaev@gmail.com", "id" : 356256, "name" : "tymerkaev@gmail.com", "real_name" : "" }, { "email" : "wlevine@gmail.com", "id" : 68465, "name" : "wlevine@gmail.com", "real_name" : "Will Levine" } ], "cf_fx_iteration" : "---", "cf_fx_points" : "---", "cf_last_resolved" : "1998-12-12T17:06:46Z", "cf_qa_whiteboard" : "", "cf_user_story" : "", "classification" : "Graveyard", "component" : "XFE", "creation_time" : "1998-04-07T08:37:03Z", "creator" : "weitsang@cs.cornell.edu", "creator_detail" : { "email" : "weitsang@cs.cornell.edu", "id" : 55, "name" : "weitsang@cs.cornell.edu", "real_name" : "" }, "depends_on" : [], "dupe_of" : null, "flags" : [], "groups" : [], "id" : 35, "is_cc_accessible" : true, "is_confirmed" : true, "is_creator_accessible" : true, "is_open" : false, "keywords" : [], "last_change_time" : "2013-11-20T02:16:47Z", "mentors" : [], "mentors_detail" : [], "op_sys" : "Solaris", "platform" : "Sun", "priority" : "P3", "product" : "MozillaClassic", "qa_contact" : "", "resolution" : "WONTFIX", "see_also" : [], "severity" : "minor", "status" : "VERIFIED", "summary" : "Navigator does not free preference hash table when exit.", "target_milestone" : "---", "url" : "", "version" : "1998-03-31", "votes" : 0, "whiteboard" : "" } ], "faults" : [] }
Fetching the Data
Reading from an input stream can be accomplished in a variety of ways. Here’s some code that wraps the InputStreamin within a BufferedInputStream so that the data may be read one line at a time. This is accomplished by appending each line to a StringBuilder and then converting it into a String once the entire file has been read.
HttpURLConnection urlConnection; StringBuilder result = new StringBuilder(); try { URL url = new URL("https://bugzilla.mozilla.org/rest/bug/35"); urlConnection = (HttpURLConnection) url.openConnection(); InputStream in = new BufferedInputStream(urlConnection.getInputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line; while ((line = reader.readLine()) != null) { result.append(line); } String input = result.toString(); //processing code will go here... }catch( Exception e) { e.printStackTrace(); } finally { urlConnection.disconnect(); }
Step 2: Processing the JSONObject
The Bugzilla feed returns a serialized JSON object with two keys: “bugs” and “faults”. Once the data is converted into a JSONObject, we can reference the “bugs” key by name to fetch its JSONArray of bug objects. In this case, the element contains only one element for bug #35. Processing of the array is handled by the iterateOverJsonArray() function because it will be required again further down the line.
JSONObject jo = new JSONObject(input); JSONArray bugs = jo.getJSONArray("bugs"); iterateOverJsonArray(bugs);
Inside the iterateOverJsonArray() function, a for object loop iterates over each element (again, one element in this instance), and checks whether the current attribute is a JSONObject or a native type such as a number or string.
private static void iterateOverJsonArray(JSONArray ja) { for (Object o: ja) { if ( o instanceof JSONObject ) { iterateOverJsonItems( (JSONObject)o ); } else { System.out.println(o); } } }
The iterateOverJsonItems() function is where the real work takes place. The JSONObject has a keys() function that returns an Iterator for, you guessed it, iterating over each key. Within the while loop, we can check each value’s type using the instanceof operator. If the object is a nested JSONArray or JSONObject, it is passed on to the relevant function for further processing; otherwise, the current value is outputted to the console. Notice the use of casting to get at the underlying object type.
private static void iterateOverJsonItems( JSONObject jo ) { Iterator<String> keys = jo.keys(); while( keys.hasNext() ) { String key = (String)keys.next(); Object value = jo.get(key); System.out.print(key + ": "); if ( value instanceof JSONArray ) { System.out.print("["); if ( ((JSONArray) value).length() > 0 ) { System.out.print("n"); iterateOverJsonArray( (JSONArray)value ); System.out.println("]"); } else { System.out.print("]n"); } } else if ( value instanceof JSONObject ) { System.out.println("{"); iterateOverJsonItems( (JSONObject)value ); System.out.println("}"); } else { System.out.println(value); } } }
Here is a portion of the program output. The exact format is not as important as the exercise of utilizing the JSONObject and JSONArray classes to work with the data.
creation_time: 1998-04-07T08:37:03Z keywords: [] is_cc_accessible: true flags: [] whiteboard: resolution: WONTFIX see_also: [] platform: Sun cf_fx_points: --- cf_fx_iteration: --- alias: firstBug id: 35 assigned_to: mcafee@gmail.com dupe_of: null cc: [ hchang@mozilla.com rexyrexy2@gmail.com tymerkaev@gmail.com wlevine@gmail.com ]
Conclusion
Although Android’s JSONObject and JSONArray classes make it easy to extract the bits of the data that we need, they also require the entire object to be read into a String first. For a very large data set, this will consume a lot of memory. In fact, attempting to input too large a dataset can cause a java.lang.OutOfMemoryError. Several third-party parsers have come about to deal with this problem. We’ll take a look at some of these in future articles.