Patterns of Reading JSON
JavaScriptObjectNotion (JSON) is a de-facto standard for exchanging data over Web API. JSON is a recursive data structure and can be visualized as a key-value pairs tree.
There are two broad categories of APIs for reading information from JSON :
- Streaming Based: It is parsing or visiting of JSON part by part. With each part iteration, you skip or map part to Object/s. Part maybe
{
,[
, field, value,:
,}
, and]
(JSON Tokens!). - Binding based: Binding is mapping of JSON field to Programming Objects; these may be domain or abstract.JSON is entirely parsed and mapped to objects. This article goes through different patterns of Reading JSON data. Java is chosen for demonstrating these patterns.
JSON — Abstract Model Binding
JSON is mapped to the library provided objects or collections objects intermediately, then these objects are iterated for mapping with domain objects or for extracting information. Formally, these intermediary objects are collectively called Parsed Tree³. The process of converting JSON to Parsed Tree is known as parsing². These objects do not adequately express business knowledge; this is the reason for calling these objects: abstract.
In Action
Consider the extraction distance between New York and Washington from the output of a sample Google Distance Matrix API :
{
"destination_addresses" : [ "New York, NY, USA" ],
"origin_addresses" : [ "Washington, DC, USA" ],
"rows" : [
{
"elements" : [
{
"distance" : {
"text" : "225 mi",
"value" : 361715
},
"duration" : {
"text" : "3 hours 49 mins",
"value" : 13725
},
"status" : "OK"
}
]
}
],
"status" : "OK"
}
GSON is one of the libraries to parse JSON. The code for extracting distance is as:
JsonParser parser = new JsonParser();
// tree object structure is provided by lib.
JsonElement parsedTree = parser.parse(json);
JsonObject root = parsedTree.getAsJsonObject();
JsonObject firstRow = root.get("rows").getAsJsonArray().get(0).getAsJsonObject();
JsonObject firstElement = firstRow.get("elements").getAsJsonArray().get(0).getAsJsonObject();
// finally, distance is extracted ! Phew
String distance = firstElement.get("distance").getAsJsonObject().get("value").getAsString();
Repl.it → https://repl.it/@DM8tyProgrammer/NonDomainBinding
JSON — Domain Model Binding
Almost whole JSON is mapped directly to equivalent Domain or Business Objects; Domain object mimics the structure of JSON with identical fields and compatible types.
In Action
Consider Twitter’s Tweet object from Web API for a demonstration of this pattern.
{
"created_at": "Wed Oct 10 20:19:24 +0000 2018",
"id": 1050118621198921728,
"id_str": “1050118621198921728",
"text": "To make room for more expression, we will now count all emojis as equal—including those with gender and skin t… https://t.co/MkGjXf9aXm",
"user": {…}
.
.
.
}
The equivalent class would be for the above JSON:
class Tweet {
private long id;
private String id_str;
private String text;
private String source;
private User user;
private String created_at;
}
{ "user": {
"id": 6253282,
"id_str": "6253282",
"name": "Twitter API",
"screen_name": "TwitterAPI",
"location": "San Francisco, CA",
"url": "https://developer.twitter.com",
"description": "The Real Twitter API. Tweets about API changes, service issues and our Developer Platform. Don’t get an answer? It’s on my website."
.
}
}
class User {
private long id;
private String id_str;
private String name;
private String description;
private String screen_name;
private String url;
private String location;
}
You can verify: Classes mirror JSON field by field with the same name and compatible types. Some libraries offer relaxations in mappings: missing fields, unknown fields, field name mismatch, type mismatch, and conversion.
Jackson is a famous library for domain object mapping. Jackson provides ObjectMapper API to read or write JSON. The code of mapping is as:
ObjectMapper objectMapper = new ObjectMapper();
Tweet tweet = objectMapper.readValue(tweetAsJson, Tweet.class);
// process Tweet object
Repl.it → https://repl.it/@DM8tyProgrammer/jackson
Expression Based Data Extraction
Selected data from JSON can be extracted using JSONPath, which is an algebraic expression for pointing fields. XPath inspires the idea of JSONPath. Extraction using JSONPath can be described as:
- JSON Parsing: JSON is parsed into Abstract Objects or Parsed Tree.
- Expression Evaluation: An expression is passed to API exposed by the previous step to query JSON.
JSONPath Expression
In JSONPath context, JSON is considered as Tree. The below elements are concatenated to formulate expression:
- The root of JSON is selected by
$
. .
selects the immediate child field...
selects any level child field.[‘<field name>’]
or just name to select field.[<number>]
to access the nth array element.[*]
targets all fields of an array.
In Action
Again, consider the extraction distance between New York and Washington from the output of a sample Google Distance Matrix API :
{
"destination_addresses" : [ "New York, NY, USA" ],
"origin_addresses" : [ "Washington, DC, USA" ],
"rows" : [
{
"elements" : [
{
"distance" : {
"text" : "225 mi",
"value" : 361715
},
"duration" : {
"text" : "3 hours 49 mins",
"value" : 13725
},
"status" : "OK"
}
]
}
],
"status" : "OK"
}
The JSON path would be: $.rows[0].elements[0].distance.value.
JSONPath can be evaluated with Jayway’s Jsonpath (notice name!) library.The code snippet for the extracting distance is as:
ReadContext ctx = JsonPath.parse(distanceJson);
Double distance = ctx.read("$.rows[0].elements[0].distance.value", Double.class);
Repl.it → https://repl.it/@DM8tyProgrammer/jsonpath
JSON Streaming Parsing
Aforementioned, streaming JSON means iterating JSON part by part. These parts are formally known as tokens. These may be a single character or group of characters. In JSON Format Specification , the following tokens are defined:
- Object Sentinels::
{
:Start of Object,}
: End of Object - Array Sentinels::
[
: Start of Array,]
: End of Array - Literal Types:: Number, String, Boolean (
true
,false
),null
.
In Action
Consider a simple JSON to parse:
{
"name": "Mighty",
"handler": "DM8typrogrammer"
}
GSON and Jackson both featured Streaming API. Jackson is arbitrarily chosen for demonstrating. The code of parsing JSON through streaming tokens is showcased below; with each iteration data, is pushed to collections as:
String json = "{"name": "mighty", "handler": "DM8typrogrammer"}";
JsonFactory jsonfactory = new JsonFactory();
JsonParser jsonParser = jsonfactory.createParser(json);
// putting values in map
Map<String, String> data = new HashMap<>();
JsonToken currentToken = jsonParser.nextToken();
while (currentToken != JsonToken.END_OBJECT) {
if (currentToken == JsonToken.FIELD_NAME) {
String fieldname = jsonParser.getCurrentName();
jsonParser.nextToken(); // move to value
// pushing data to map
data.put(fieldname, jsonParser.getText());
}
// iterating token by token
currentToken = jsonParser.nextToken();
}
jsonParser.close();
System.out.println(data);
Repl.it → https://repl.it/@DM8tyProgrammer/stream-json The code can be understood as iterating though tape having JSON tokens:
Closing Notes
Binding API internally streams JSON for converting it into object form. It can be said Binding is a High-Level API.
Streaming, Abstract Model Binding, and Expression Based Extraction are used toselect a part of data from JSON. Domain Model Binding method is used to extract the complete JSON information.
Expression-based JSON extraction is a declarative variantof Abstract Model Binding method; Instead of you writing code by hand for walking Parsed Tree, you are passing specifications to the API to extract data from JSON. It is a concise and flexible method; however, it is a slower variant.
Footnotes
- Reading JSON means Parsing² of JSON and mapping the data into Domain Objects.
- Parsing is a mechanism of converting formatted text into a data structure. In case of JSON parsing, the output data structure is of Tree type.
- Parsed Tree is the output data structure of JSON parsing.
References
- Introducing JSON, https://www.json.org/json-en.html.
- JSONPath — XPath for JSON, https://goessner.net/articles/JsonPath.
- Douglas Crockford — https://www.crockford.com/mckeeman.html