[Android] Parsing JSON with JsonReader

Back in Parsing JSON with JSONObject, I covered parsing JSON using JSONObject, where the object is created after reading the entire data into a String. Here, the same application will be created, but it will JsonReader instead. The source code for the demo can be downloaded from the Github repository.

Unlike JSONObject, JsonReader’s constructor takes in a Reader. Instead of parsing the data up front (as in JSONObject or ), the data is streamed in JsonReader. Using the same sample JSON data as before (see Parsing JSON with JSONObject), the following method constructs a reader and the JsonReader for parsing the data:

private void loadData() {
    JsonReader reader = null;
    try {
        InputStream inStream = getResources().openRawResource(R.raw.json);

        BufferedInputStream bufferedStream = new BufferedInputStream(
                inStream);
        InputStreamReader streamReader = new InputStreamReader(
                bufferedStream);

        reader = new JsonReader(streamReader);

        populateTable(reader);
    } catch (Exception e) {
        Log.e("Json Sample", e.getLocalizedMessage(), e);
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                // Do nothing
            }
        }
    }
}

Since the data is streamed via the a Reader, data has to be read sequentially. JsonReader provides methods for traversing the contents. To access the observation data, it will have to skip over the _header_ and _notice_ arrays. The following methods will help to skip over the bits of data that is not used:

private boolean findNextTokenType(JsonReader reader, JsonToken type)
        throws IOException {

    JsonToken token = reader.peek();
    while (token != JsonToken.END_DOCUMENT) {
        if (token == type) {
            return true;
        }

        consume(reader, token);
        token = reader.peek();
    }

    return false;
}

private void consume(JsonReader reader, JsonToken type) throws IOException {
    switch (type) {
    case BEGIN_ARRAY:
        reader.beginArray();
        break;
    cas<a href="http://developer.android.com/reference/org/json/JSONObject.html" target="_blank">JSONObject</a>e BEGIN_OBJECT:
        reader.beginObject();
        break;
    case END_ARRAY:
        reader.endArray();
        break;
    case END_OBJECT:
        reader.endObject();
        break;
    default: <a href="http://developer.android.com/reference/org/json/JSONObject.html" target="_blank">JSONObject</a>
        reader.skipValue();
    }
}


The <b>findNextTokenType</b> repeatedly uses <b>consume</b> to consume the tokens in the stream until it finds the next occurrence of the given token type of until the document ends. The boolean return value will indicate whether the next occurrence was found or not. The observation data is located in an array called "data". The following method will use <b>findNextTokenType</b> to find this array:

private boolean findArray(JsonReader reader, String objectName)
        throws IOException {
    while (findNextTokenType(reader, JsonToken.NAME)) {

        String name = reader.nextName();
        if (name.equals(objectName)) {
            JsonToken token = reader.peek();
            if (token == JsonToken.BEGIN_ARRAY) {
                return true;
            }
        }
    }

    return false;
}

Loop over each of the observations and put it in the table:

private void parseDataArray(JsonReader reader) throws IOException {
    final TableLayout table = (TableLayout) findViewById(R.id.table);
    reader.beginArray();

    JsonToken token = reader.peek();
    while (token != JsonToken.END_ARRAY) {
        parseDataObject(reader, table);
        token = reader.peek();
    }
}

private void parseDataObject(JsonReader reader, final TableLayout table)
        throws IOException {
    if (findNextTokenType(reader, JsonToken.BEGIN_OBJECT)) {
        final View row = getLayoutInflater().inflate(R.layout.rows, null);

        reader.beginObject();
        while (reader.hasNext()) {
            parseData(reader, row);
        }

        table.post(new Runnable() {

            public void run() {
                table.addView(row);
            }
        });

        // Consume end of object so that we can just look for the start of
        // the next object on the next call.
        if (findNextTokenType(reader, JsonToken.END_OBJECT)) {
            reader.endObject();
        }
    }
}

private void parseData(JsonReader reader, View row) throws IOException {
    int columnId = toRowId(reader.nextName());

    if (columnId != -1) {
        ((TextView) row.findViewById(columnId))
                .setText(reader.nextString());
    } else {
        consume(reader, reader.peek());
    }
}

private int toRowId(String name) {
    if (name.equals("local_date_time_full")) {
        return R.id.localTime;
    } else if (name.equals("apparent_t")) {
        return R.id.apprentTemp;
    } else if (name.equals("wind_spd_kmh")) {
        return R.id.windSpeed;
    }
    return -1;
}

The method parseDataObject looks for the next object and creates the row if it finds one. parseData decides if the field should be added to the table or not. If the field is not to be added, it will consume the field from the stream. The method toRowId determines which of the cells in the row that the field should be placed in. It will either return id of the cell or -1 if the field is not to be added.

JsonReader vs JSONObject

The difference in the way the data is parsed makes the implementation using JsonReader more complex than the one using JSONObject. With the data being streamed in JsonReader, you would also expect the JsonReader implementation to more efficient than the JSONObject implementation. This was confirmed by obtaining some trace data from the demos (see JsonReader.trace and JsonObject.trace). The resulting data (click on the image to get a better view):

The panel on the left displays the trace data from the JSONObject and the right is for JsonReader. The highlighted loadData method is one responsible for reading the data into the table. Note that JSONObject’s implementation took about 771ms of the CPU time, where as JsonReader took about 485ms – a difference of about 37%.

Another aspect to consider is memory consumption. The entire data must be read into a String for JSONObject, whereas the data is streamed in the case of JsonReader. The JSONObject approach would also use more memory to create the objects to represent the other objects and arrays. Certainly, there are large JSON data sets out there – the data used in the demos from the Australian Bureau of Meteorlogy was only a subset of a much larger 7KB!

One Response to [Android] Parsing JSON with JsonReader

  1. Pingback: [Android] Parsing JSON with JSONObject « Kah – The Developer

Leave a comment