Exploring the JSON-P API: Simplifying JSON Processing In Jakarta EE 10

Photo of Luqman Saeed by Luqman Saeed

JSON (JavaScript Object Notation) has become the de facto standard for data interchange in modern web applications. Its simplicity, readability, and compatibility have made it a popular choice for representing structured data. The JSON-P (JSON Processing) API provides a powerful and convenient way to parse, generate, transform, and query JSON data on the Jakarta EE Platform.  In this blog post, we will explore the fundamentals of the JSON-P API, its core functionalities, and how it empowers you to work with JSON effortlessly in your Jakarta EE applications. 

JSON-P Structure

The JSON-P API is part of theJakarta EE Platform, introduced in Java EE 7. It aims to provide a standard and portable way to handleJSONdata, regardless of the underlying implementation. JSON-P follows a similar design philosophy as other Jakarta EE APIs, providing a set of classes and interfaces that simplify JSON processing tasks, making it easier for you to work with JSON data. 

JSON-P has two main structures, the Object Model API and the Streaming API. The Object Model API, similar to the DOM API for XML, is designed as a high-level API that provides immutable object models for JSON object and array structures. The two core objects you can create using the Object API are jakarta.json.JsonObject and jakarta.json.JsonArray. The JsonObject is a facade around java.util.Map that gives an unordered view of name/value pairs. The JsonArray is also a facade around java.util.List that gives an ordered view of zero or more values.

The Streaming API, on the other hand, is similar to the StAX API for XML and consists of the interfaces JsonParser and JsonGenerator. JsonParser has methods for efficiently parsing large JSON files in a streaming manner, complete with event emissions that you can use to advance the parser. The JsonGenerator provides methods for writing JSON to a stream. 

The JSON-P Object Model API

Creating a JsonObject and JsonArray with the Object API is straightforward. The jakarta.json.JsonObjectBuilder and jakarta.json.JsonArrayBuilder interfaces have fluent methods for creating JsonObject and JsonArray, respectively. The code snippet below shows the creation of a JsonObject that represents the following JSON structure.

{

  "_id": "648afc8513038302c030e2ae",

  "guid": "2381ab15-584f-4a15-9fc1-583019957ecb",

  "isActive": true,

“currency”:”$”,

  "balance": 1,019.56,

  "picture": "http://placehold.it/32x32",

  "age": 38,

  "name": "Heather Pitts",

  "latitude": 37.449717,

  "longitude": 73.781669,

  "tags": [

    "duis",

    "ad",

    "eu"

  ]}




JsonObject jsonObject = Json.createObjectBuilder()

.add("_id", "648afc8513038302c030e2ae")

.add("guid", "2381ab15-584f-4a15-9fc1-583019957ecb")

.add("isActive", true)

.add("currency", "$")

.add("balance", new BigDecimal(" 1019.56"))

.add("picture", "http://placehold.it/32x32")

.add("age", 38)

.add("name", "Heather Pitts")

.add("latitude", 37.449717)

.add("longitude", 73.781669)

.add("tags",

Json.createArrayBuilder()

.add("duis")

.add("ad")

.add("eu")

.build())

.build();

The jakarta.json.Json is the entrypoint to creating the JsonObjectBuilder. The static createObjectBuilder() method returns an JsonObjectBuilder instance on which we call the add() method repeatedly to add data to our JsonObject. The add method is fluent, so we call it repeatedly until our data is fully built, then we call the .build() method. 

The JSON string has a tags array. To create that, we call the createArrayBuilder() method on the Json class to get a JsonArrayBuilder instance. Notice for the array, we only call the add() with a single argument, unlike that for the object builder. The above code will create a JsonObject, Java representation of the JSON string shown earlier. As you can see, the Object Model API is very easy and straightforward to use. This is pretty much how you will use it a lot of the time for creating JsonObjects and JsonArrays.

You can also read the above JSON string into a JsonObject. To do so, you call the createReader() method on the Json class. This method takes either a java.io.InputStream or java.io.Reader. For example the following code snippet reads the above JSON string into a JsonObject.

  try (JsonReader jsonReader = Json.createReader(new StringReader(Files.readString(Paths.get("filePath"))))) {

JsonObject readObject = jsonReader.readObject();

}

A new StringReader instance is passed to the createReader() method of the Json class, which returns a jakarta.json.JsonReader. This JsonReader has readObject() and readArray() methods that return either a JsonObject or JsonArray, depending on the structure of the read JSON string. In our example, the read JSON string is an object, so we call the readObject() to get the JsonObject.

The Object Model API also supports writing the generated JSON to an output source, either an OutputStream or Writer. For example, the JsonObject created earlier can be written to a given file as shown below.

try (JsonWriter outputWriter = Json.createWriter(new PrintWriter(Paths.get("filepath").toFile()))) {

outputWriter.writeObject(jsonObject);

}

The JSON-P Streaming API

The JSON-P Streaming API is a fairly low level API for efficiently parsing large JSON files. Unlike the Object Model API which creates a random access tree-like structure that represents the JSON data in memory, the Streaming API provides forward, read-only access to JSON data using the pull parsing programming model. This makes it much more efficient for parsing and querying large JSON files. 

The jakarta.json.stream.JsonParser is the primary way of using the Streaming API. Similar to the other objects, the Json class has a createParser() method that takes an InputStream or a Reader. For example, let’s parse the following JSON string and search for all objects where the “first_name” element matches a given pattern.

[

  {

    "id": 1,

    "first_name": "Donnell",

    "last_name": "Lisciardelli",

    "email": "dlisciardelli0@zimbio.com",

    "gender": "Male",

    "ip_address": "125.244.163.191",

    "tags": "ID-MA"

  },

  {

    "id": 2,

    "first_name": "Nelly",

    "last_name": "Lorking",

    "email": "nlorking1@quantcast.com",

    "gender": "Female",

    "ip_address": "129.244.12.121",

    "tags": "GL-U-A"

  },

  {

    "id": 3,

    "first_name": "Seamus",

    "last_name": "Dugdale",

    "email": "sdugdale2@networkadvertising.org",

    "gender": "Male",

    "ip_address": "241.211.21.226",

    "tags": "MY-13"

  },

  {

    "id": 4,

    "first_name": "Jemima",

    "last_name": "Dring",

    "email": "jdring3@sbwire.com",

    "gender": "Female",

    "ip_address": "128.113.101.121",

    "tags": "FR-V"

  },

  {

    "id": 5,

    "first_name": "Nelie",

    "last_name": "Sales",

    "email": "nsales4@github.io",

    "gender": "Female",

    "ip_address": "176.229.130.129",

    "tags": "AU-WA"

  }

]



public List<JsonObject> filterJson(String name) {

List<JsonObject> jsonObjects = new ArrayList<>();


final String regex = "(?i).*" + name + ".*";

try (JsonParser parser = Json.createParser(new StringReader("data.json"))) {


while (parser.hasNext()) {

JsonParser.Event next = parser.next();

if (next == JsonParser.Event.START_OBJECT) {

JsonObject object = parser.getObject();

if (object.getString("first_name").matches(regex)) {

jsonObjects.add(object);

}

}
}

}

return jsonObjects;

}

The method filterJson takes a string from which it creates a regex to match any part of the target value. It then creates a JsonParser by calling the createParser method with a StringReader instance. The JsonParser uses a forward looking read only approach to accessing the parsed file. The hasNext() method returns true if there is a parsing state. A parsing state refers to any part of a non-emptyJSON document. Knowing this, we create a while loop and walk through each “token” of the document.

For each token, we check for the JsonParser.Event type. Each token is associated with an event as defined in the Event enum. Using events, you can listen for when a specific part of the document is about to be parsed. In the filterJson method, we listen for the START_OBJECT event. This event is emitted at the start of parsing of an event, when the cursor of the parser is after the opening curly brace. You get the associated event by calling the parser.hasNext() method.

If the current parse state event is a START_OBJECT event, we call the parser.getObject() to get the JsonObject then match the first_name element to the created regex. If it matches, then the JsonObject is added to a list. After the entire JSON document is parsed, the list will contain JsonObjects whose first_name elements match the parsed name string in any form. As you can see, the JSON-P Streaming model is much suited to parsing and filtering JSON files in a much more flexible and efficient but verbose way. 

Conclusion

Both the Object and Streaming models of the JSON-P API together give you a well balanced set of options for processing JSON documents in your Jakarta EE applications. Shipped as part of the platform, it is ready to use without you needing to explicitly declare it as a dependency. You can use it to augment the underlying marshaller of Jakarta REST to create very powerful web services. Together with its twin spec, the JSON-B API,  you have all you need for complete JSON processing on the Jakarta EE Platform.

Want to learn more about JSON? We have a full guide on the subject:

The Complete Guide To JSON Processing On the Jakarta EE Platform

Comments