Hands On with the New Native JSON Parser

The Apex JSONObject class, part of apex-library, has long been a staple ingredient in any Force.com mashup; indeed, I made extensive use of it in my Dreamforce 2011 session Mashing Up the Cloud with Force.com Toolkits. Based on the Java original, JSONObject works well, except for one aspect – you quickly hit the script statement limit on moderately large JSON objects, since several statements are executed for each character of input.

As Quinton mentioned a couple of weeks ago, one of the most eagerly awaited features of the Winter 12 release is a native JSON implementation. Since a bunch of the sandboxes cut over to Winter 12[1] last weekend, I decided to spend some time this week porting my embryonic Force.com Toolkit for SimpleGeo to native JSON.

The first thing to recognize is that the native JSON implementation is based on a streaming model, similar to the StAX (Streaming API for XML) parser. Rather than parse the entire input into a tree of objects, you ‘pull’ data from the parser. The docs page for JSONParser shows how to extract latitude and longitude from a Google Maps query. Note, the code in the docs page will likely fail, since, for this (free) API, Google imposes a limit of 2,500 geolocation requests per day per originating IP address. To work around this, I captured the API response and created a ‘canned’ version that works just fine.

As you can see from the sample code, although the streaming model is very different from JSONObject, it’s pretty straightforward. Here’s some code from the ‘old’ SimpleGeo toolkit to pull data from JSONObject into an instance of an Apex class:

public JsonClassifier(JSONObject jsonObj) {
    category = jsonObj.getValue('category').str;
    typ = (jsonObj.getValue('type') != null)
        ? jsonObj.getValue('type').str : null;
    subcategory = (jsonObj.getValue('subcategory') != null)
        ? jsonObj.getValue('subcategory').str : null;
}

Here’s the new code, pulling tokens from the new JSONParser:

public JsonClassifier(JSONParser parser) {
    while (parser.nextToken() != JSONToken.END_OBJECT) {
        if (parser.getCurrentToken() == JSONToken.FIELD_NAME) {
            String text = parser.getText();
            parser.nextToken();
            if (text == 'category') {
                category = parser.getText();
            } else if (text == 'type') {
                typ = parser.getText();
            } else if (text == 'subcategory') {
                subcategory = parser.getText();
            } else {
                System.debug(LoggingLevel.WARN, 'JsonClassifier consuming unrecognized property: '+text);
                consumeObject(parser);
            }
        }
    }
}

There is a bit more code there, but it’s not rocket science – we pull tokens from the parser and, for each field we recognize, we set an instance variable. For a measure of future-proofing, if we encounter an unrecognized field, we simply skip it, using a consumeObject() helper function defined thus:

private static void consumeObject(JSONParser parser) {
    Integer depth = 0;
    do {
        JSONToken curr = parser.getCurrentToken();
        if (curr == JSONToken.START_OBJECT ||
            curr == JSONToken.START_ARRAY) {
            depth++;
        } else if (curr == JSONToken.END_OBJECT ||
            curr == JSONToken.END_ARRAY) {
            depth--;
        }
    } while (depth > 0 && parser.nextToken() != null);
}

I must admit, I was tempted to simply reimplement JSONObject using JSONParser, rather than rewriting my SimpleGeo code, but that would have sacrificed much of the time/space performance benefit of the streaming parser, even if it did stay within the script statement limit. I haven’t done any performance tests yet, but the new implementation certainly feels much faster, and consumes a fraction of the script statements – running a simple test with SimpleGeo.getContextFromIPAddress(), the new code consumes 1587 statements, against 136480 for JSONObject – an 85-fold improvement!

So, take the new native JSON for a spin – you can use the SimpleGeo.cls diffs as a guide to porting a moderately complex implementation.

[1] You can quickly check if your sandbox instance is on Winter ’12 with the following command:

curl https://cs4.salesforce.com/services/data -H "X-PrettyPrint: 1"

If you see Winter ’12 in the output, then you’re all set to go. Although you can, of course, sign up for the Winter ’12 pre-release, the security rules on the pre-release orgs don’t allow callouts to arbitrary sites, so I’ve found that a sandbox is a better bet for this sort of work.

Published
September 23, 2011

Leave your comments...

Hands On with the New Native JSON Parser