11 Jakarta JSON Processing

WebLogic Server supports Jakarta JSON Processing 2.1 (JSON-P) for parsing, generating, querying and transforming JSON data. This release includes the Jakarta JSON Processing API and the Eclipse Parsson implementation. Jakarta JSON Processing 2.1

See Jakarta JSON Processing 2.1.

To learn more about JSON concepts, see Jakarta JSON Processing in the Jakarta EE Tutorial.

About JavaScript Object Notation (JSON)

JSON is a lightweight data-interchange format that is widely used as a common format to serialize and deserialize data in applications that communicate with each other over the Internet. These applications are often created using different programming languages and run in very different environments.

JSON is suited to this scenario because it is an open standard, it is easy to read and write, and it is more compact than other representations. RESTful web services typically make extensive use of JSON as the format for the data inside requests and responses, with the JSON representations usually being more compact than the counterpart XML representations since JSON does not have closing tags.

Jakarta JSON Processing provides a convenient way to process (parse, generate, transform, and query) JSON text. For generating and parsing JSON data, there are two programming models, which are similar to those used for XML documents:

  • The object model creates a tree that represents the JSON data in memory. The tree can then be navigated and analyzed. Although the JSON data created in memory is immutable and cannot be modified, the object model is the most flexible and allows for processing that requires access to the complete contents of the tree. However, it is often slower than the streaming model and requires more memory. The object model generates JSON output by navigating the entire tree at once.

    For information about using the object model, see Object Model API.

  • The streaming model uses an event-based parser that reads JSON data one element at a time. The parser generates events and stops for processing when an object or an array begins or ends, when it finds a key, or when it finds a value. Each element can be processed or discarded by the application code, and then the parser proceeds to the next event. This approach is adequate for local processing, in which the processing of an element does not require information from the rest of the data. The streaming model generates JSON output to a given stream by making a function call with one element at a time.

    For information about using the streaming model, see Streaming API.

Object Model API

The object model API is a high-level API that provides immutable object models for JSON object and array structures.

These JSON structures are represented as object models using the Java types JsonObject and JsonArray. The interface jakarta.json.JsonObject provides a map view to access the unordered collection of zero or more name-value pairs from the model. Similarly, the jakarta.json.JsonArray interface provides a list view to access the ordered sequence of zero or more values from the model.

The object model API uses builder patterns to create these object models. The jakarta.json.JsonObjectBuilder and jakarta.json.JsonArrayBuilder interfaces provide methods to create models of type JsonObject and JsonArray, respectively.

These object models can also be created from an input source using the jakarta.json.JsonReader interface. Similarly, these object models can be written to an output source using the jakarta.json.JsonWriter interface.

The following sections show examples of using the object model API:

Creating an Object Model from JSON Data

For a UTF-8 file whose top-level value is an object or array, read the model as follows. The caller handles IOException and parsing failures.

import jakarta.json.Json;
import jakarta.json.JsonReader;
import jakarta.json.JsonStructure;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class JsonReadExample {
    public static JsonStructure read(Path file) throws IOException {
        try (Reader input = Files.newBufferedReader(file, StandardCharsets.UTF_8);
             JsonReader reader = Json.createReader(input)) {
            return reader.read();
        }
    }
}
JsonStructure jsonst = JsonReadExample.read(Path.of("jsondata.txt"));

Usage from a caller importing java.nio.file.Path and jakarta.json.JsonStructure and handling or declaring IOException:

The object reference jsonst can be either of type JsonObject or of type JsonArray, depending on the contents of the file. JsonObject and JsonArray are subtypes of JsonStructure. This reference represents the top of the tree and can be used to navigate the tree or to write it to a stream as JSON data.

Creating an Object Model from Application Code

The following example shows how to create an object model from application code:

import jakarta.json.Json;
import jakarta.json.JsonObject;
...
JsonObject model = Json.createObjectBuilder()
   .add("firstName", "Duke")
   .add("lastName", "Java")
   .add("age", 18)
   .add("streetAddress", "100 Internet Dr")
   .add("city", "JavaTown")
   .add("state", "JA")
   .add("postalCode", "12345")
   .add("phoneNumbers", Json.createArrayBuilder()
      .add(Json.createObjectBuilder()
         .add("type", "mobile")
         .add("number", "111-111-1111"))
      .add(Json.createObjectBuilder()
         .add("type", "home")
         .add("number", "222-222-2222")))
   .build();

The object reference model represents the top of the tree, which is created by nesting invocations to the add methods and is built by invoking the build method. The jakarta.json.JsonObjectBuilder interface contains the following add methods:

JsonObjectBuilder add(String name, BigDecimal value)
JsonObjectBuilder add(String name, BigInteger value)
JsonObjectBuilder add(String name, boolean value)
JsonObjectBuilder add(String name, double value)
JsonObjectBuilder add(String name, int value)
JsonObjectBuilder add(String name, JsonArrayBuilder builder)
JsonObjectBuilder add(String name, JsonObjectBuilder builder)
JsonObjectBuilder add(String name, JsonValue value)
JsonObjectBuilder add(String name, long value)
JsonObjectBuilder add(String name, String value)
JsonObjectBuilder addNull(String name)

The jakarta.json.JsonArrayBuilder interface contains similar add methods that do not have a name (key) parameter. You can nest arrays and objects by passing a new JsonArrayBuilder object or a new JsonObjectBuilder object to the corresponding add method, as shown in this example.

The resulting tree represents the JSON data from JSON Syntax.

Navigating an Object Model

The following example shows a simple approach to navigating an object model:

import jakarta.json.JsonValue;
import jakarta.json.JsonObject;
import jakarta.json.JsonArray;
import jakarta.json.JsonNumber;
import jakarta.json.JsonString;
...
public static void navigateTree(JsonValue tree, String key) {
   if (key != null)
      System.out.print("Key " + key + ": ");
   switch(tree.getValueType()) {
      case OBJECT:
         System.out.println("OBJECT");
         JsonObject object = (JsonObject) tree;
         for (String name : object.keySet())
            navigateTree(object.get(name), name);
         break;
      case ARRAY:
         System.out.println("ARRAY");
         JsonArray array = (JsonArray) tree;
         for (JsonValue val : array)
            navigateTree(val, null);
         break;
      case STRING:
         JsonString st = (JsonString) tree;
         System.out.println("STRING " + st.getString());
         break;
      case NUMBER:
         JsonNumber num = (JsonNumber) tree;
         System.out.println("NUMBER " + num.toString());
         break;
      case TRUE:
      case FALSE:
      case NULL:
         System.out.println(tree.getValueType().toString());
         break;
   }
}

The navigateTree method can be used with the models shown in Creating an Object Model from JSON Data and Creating an Object Model from Application Code as follows:

navigateTree(model, null);

The navigateTree method takes two arguments: a JSON element and a key. The key is used only to help print the key-value pairs inside objects. Elements in a tree are represented by the JsonValue type. If the element is an object or an array, a new invocation to this method is made for every element contained in the object or array. If the element is a value, it is printed to standard output.

The JsonValue.getValueType method identifies the element as an object, an array, or a value. For objects, the JsonObject.keySet method returns a set of strings that contains the keys in the object, and the JsonObject.get(String name) method returns the value of the element whose key is name. For arrays, JsonArray implements the List<JsonValue> interface. You can use enhanced for loops with the Set<String> instance returned by JsonObject.keySet and with instances of JsonArray, as shown in this example.

The navigateTree method for the model shown in Creating an Object Model from Application Code produces the following output:

OBJECT
Key firstName: STRING Duke
Key lastName: STRING Java
Key age: NUMBER 18
Key streetAddress: STRING 100 Internet Dr
Key city: STRING JavaTown
Key state: STRING JA
Key postalCode: STRING 12345
Key phoneNumbers: ARRAY
OBJECT
Key type: STRING mobile
Key number: STRING 111-111-1111
OBJECT
Key type: STRING home
Key number: STRING 222-222-2222

Writing an Object Model to a Stream

The object models created in Creating an Object Model from JSON Data and Creating an Object Model from Application Code can be written to a stream using the jakarta.json.JsonWriter interface as follows:

import java.io.StringWriter;
import jakarta.json.JsonWriter;
...
StringWriter stWriter = new StringWriter();
JsonWriter jsonWriter = Json.createWriter(stWriter);
jsonWriter.writeObject(model);
jsonWriter.close();
 
String jsonData = stWriter.toString();
System.out.println(jsonData);
 
The Json.createWriter method takes an output stream as a parameter. The JsonWriter.writeObject method writes the object to the stream. The JsonWriter.close method closes the underlying output stream.
 
The following example uses try-with-resources to close the JSON writer automatically:
 
StringWriter stWriter = new StringWriter();
try (JsonWriter jsonWriter = Json.createWriter(stWriter)) {
   jsonWriter.writeObject(model);
}
 
String jsonData = stWriter.toString();
System.out.println(jsonData);

Streaming API

The streaming API is a low-level API designed to process large amounts of JSON data efficiently.

This API consists of the following interfaces:

Interface Description
jakarta.json.stream.JsonParser

Contains methods to parse JSON in a streaming way.

This interface provides forward, read-only access to JSON data using the pull parsing programming model. In this model the application code controls the thread and calls methods in the parser interface to move the parser forward or to obtain JSON data from the current state of the parser.

jakarta.json.stream.JsonGenerator

Contains methods to write JSON to an output source in a streaming way.

This interface provides methods to write JSON to an output source. The generator writes name-value pairs in JSON objects and values in JSON arrays.

The following sections show examples of using the streaming API:

Reading JSON Data Using a Parser

The streaming API can process JSON incrementally without building a complete object model, which can reduce memory use when the application can handle events as they arrive. The following example shows how to create a JsonParser object and how to parse JSON data using events:

Pass the JSON text as jsonData; the parser is closed when processing finishes.

import jakarta.json.Json;
import jakarta.json.stream.JsonParser;
import java.io.StringReader;

public class JsonParserExample {
    public static void parse(String jsonData) {
        try (JsonParser parser = Json.createParser(new StringReader(jsonData))) {
            while (parser.hasNext()) {
                JsonParser.Event event = parser.next();
                switch (event) {
                    case KEY_NAME:
                        System.out.print(event + " " + parser.getString() + " - ");
                        break;
                    case VALUE_STRING:
                    case VALUE_NUMBER:
                        System.out.println(event + " " + parser.getString());
                        break;
                    default:
                        System.out.println(event);
                        break;
                }
            }
        }
    }
}
JsonParserExample.parse(JsonWriteExample.write(model));

This example consists of three steps:

  1. Obtain a parser instance by invoking the Json.createParser static method.
  2. Iterate over the parser events using the JsonParser.hasNext and the JsonParser.next methods.
  3. Perform local processing for each element.

The example shows the ten possible event types from the parser. The parser's next method advances it to the next event.

For the event types KEY_NAME, VALUE_STRING, and VALUE_NUMBER, you can obtain the content of the element by invoking the JsonParser.getString method.

For a VALUE_NUMBER event, getInt(), getLong() and getBigDecimal() provide numeric representations. Use isIntegralNumber() to determine whether the current number has an integral representation, and choose an accessor appropriate to the required range and precision. The following is the event output for the object from Creating an Object Model from Application Code. getInt() getLong() getBigDecimal() isIntegralNumber()

START_OBJECT
KEY_NAME firstName - VALUE_STRING Duke
KEY_NAME lastName - VALUE_STRING Java
KEY_NAME age - VALUE_NUMBER 18
KEY_NAME streetAddress - VALUE_STRING 100 Internet Dr
KEY_NAME city - VALUE_STRING JavaTown
KEY_NAME state - VALUE_STRING JA
KEY_NAME postalCode - VALUE_STRING 12345
KEY_NAME phoneNumbers - START_ARRAY
START_OBJECT
KEY_NAME type - VALUE_STRING mobile
KEY_NAME number - VALUE_STRING 111-111-1111
END_OBJECT
START_OBJECT
KEY_NAME type - VALUE_STRING home
KEY_NAME number - VALUE_STRING 222-222-2222
END_OBJECT
END_ARRAY
END_OBJECT

Writing JSON Data Using a Generator

Call JsonGeneratorExample.write(Path.of("test.txt")) from a caller that imports java.nio.file.Path and handles or declares IOException. This writes UTF-8 JSON and closes the generator and file writer on success or failure.

import jakarta.json.Json;
import jakarta.json.stream.JsonGenerator;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class JsonGeneratorExample {
    public static void write(Path file) throws IOException {
        try (Writer output = Files.newBufferedWriter(file, StandardCharsets.UTF_8);
             JsonGenerator gen = Json.createGenerator(output)) {
            gen.writeStartObject()
               .write("firstName", "Duke")
               .write("lastName", "Java")
               .write("age", 18)
               .write("streetAddress", "100 Internet Dr")
               .write("city", "JavaTown")
               .write("state", "JA")
               .write("postalCode", "12345")
               .writeStartArray("phoneNumbers")
                   .writeStartObject()
                       .write("type", "mobile")
                       .write("number", "111-111-1111")
                   .writeEnd()
                   .writeStartObject()
                       .write("type", "home")
                       .write("number", "222-222-2222")
                   .writeEnd()
               .writeEnd()
            .writeEnd();
        }
    }
}

This example obtains a JSON generator by invoking the Json.createGenerator static method, which takes a writer or an output stream as a parameter. The example writes JSON data to the test.txt file by nesting invocations to the write, writeStartArray, writeStartObject, and writeEnd methods. The JsonGenerator.close method closes the underlying writer or output stream.

New Features for JSON Processing

The jakarta.json API supports new features of JSON Processing such as JSON Pointer, JSON Patch, and JSON Merge Patch. These features can be used to retrieve, transform or manipulate values in an object model.

In this section, the following sample JSON document is used to demonstrate the new features of JSON Processing. This sample contains name-value pairs and the value for the name "phoneNumbers" used in this sample, is an array whose elements are two objects.

{
   "firstName": "Duke",
   "lastName": "Java",
   "age": 18,
   "streetAddress": "100 Internet Dr",
   "city": "JavaTown",
   "state": "JA",
   "postalCode": "12345",
   "phoneNumbers": [
      { "Mobile": "111-111-1111" },
      { "Home": "222-222-2222" }
   ]
}

This section includes the following topics:

JSON Pointer

JSON Pointer defines a string syntax for referencing a location in the target.

A JSON Pointer identifies a location in a JSON document. The Jakarta JsonPointer methods operate on a JsonStructure, such as a JsonObject or JsonArray. An empty pointer string refers to the entire target structure.

Pass the exact sample JSON text from the beginning of this section as jsonString. Call PointerExample.apply(jsonString) to run these operations in order.

import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonPointer;
import jakarta.json.JsonReader;
import jakarta.json.JsonValue;
import java.io.StringReader;

public class PointerExample {
    public static JsonObject apply(String jsonString) {
        JsonObject contacts;
        try (JsonReader reader = Json.createReader(new StringReader(jsonString))) {
            contacts = reader.readObject();
        }
        JsonPointer email = Json.createPointer("/email");
        contacts = email.add(contacts, Json.createValue("duke@example.com"));

        JsonPointer name = Json.createPointer("/firstName");
        boolean exists = name.containsValue(contacts);

        JsonPointer phone = Json.createPointer("/phoneNumbers/0");
        JsonValue mobileNumber = phone.getValue(contacts);
        contacts = phone.remove(contacts);

        JsonPointer age = Json.createPointer("/age");
        contacts = age.replace(contacts, Json.createValue(30));
        return contacts;
    }
}

exists is true because /firstName exists. mobileNumber is the object {"Mobile":"111-111-1111"}. Every modifying method returns a new structure and its result must be assigned. containsValue tests whether a location exists, not equality with John. JSON object member order is not significant.

The resulting JSON has age 30, the added email and only the Home entry:

{
   "firstName": "Duke",
   "lastName": "Java",
   "age": 30,
   "email": "duke@example.com",
   "streetAddress": "100 Internet Dr",
   "city": "JavaTown",
   "state": "JA",
   "postalCode": "12345",
   "phoneNumbers": [
      { "Home": "222-222-2222" }
   ]
}

JSON Patch

JSON Patch defines a format for expressing a sequence of operations to be applied to a JSON document.
JsonPatch mainly consists of two interfaces:
  • JsonPatch - Provides apply(), toJsonArray() methods.
  • JsonPatchBuilder - Provides add(), copy(), move(), replace(), remove(), and test() methods.

Parse the original sample at the start of this section into a JsonObject named contacts. Pass that original object independently to usingBuilder and usingArray; these are two separate demonstrations, not successive patches. No buildPerson helper is required.

Constructing a JSON Patch with JsonPatchBuilder; constructing a JSON Patch from a JsonArray:

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonPatch;

public class PatchExamples {
    public static JsonObject usingBuilder(JsonObject contacts) {
        JsonPatch patch = Json.createPatchBuilder()
            .add("/email", "duke@example.com")
            .replace("/age", 30)
            .remove("/phoneNumbers")
            .test("/firstName", "Duke")
            .copy("/lastNameCopy", "/lastName")
            .build();
        return patch.apply(contacts);
    }

    public static JsonArray replacementOperations() {
        return Json.createArrayBuilder()
            .add(Json.createObjectBuilder()
                .add("op", "replace")
                .add("path", "/age")
                .add("value", 30))
            .build();
    }

    public static JsonObject usingArray(JsonObject contacts) {
        JsonPatch patch = Json.createPatch(replacementOperations());
        return patch.apply(contacts);
    }
}

usingBuilder adds the email, changes age to 30, removes phoneNumbers, verifies firstName is Duke, and copies lastName to the new root member lastNameCopy. In copy(path, from), the destination path comes first. usingArray changes only age to 30 and preserves both phone entries.

Patch document: [{"op":"replace","path":"/age","value":30}]

JSON Merge Patch

JSON Merge Patch defines a format and processing rules for applying operations to a JSON document that are based upon specific content of the target document.

JsonMergePatch describes changes to be made to a target JSON document using a syntax that closely mimics the document being modified.

Table 11-1 JsonMergePatch syntax

Original Patch Result
{"a":"b"} {"a":"c"} {"a":"c"}
{"a":"b"} {"b":"c"} {"a":"b","b":"c"}
{"a":"b"} {"a":null} {}
{"a":"b","b":"c"} {"a":null} {"b":"c"}

In an object merge patch, a member with a null value removes that member from the target. Arrays are replaced as complete values; their elements are not merged individually. Use JSON Patch when you need explicit operations on individual array elements or to set an object member to JSON null.

You can create a JSON Merge Patch from:
  • An existing JSON Merge Patch document represented as a JsonValueJSONMergePatch
    JsonValue contacts = ... ; // The target to be patched
    JsonValue patch = ...  ; // JSON Merge Patch
    JsonMergePatch mergePatch = Json.createMergePatch(patch);
    JsonValue result = mergePatch.apply(contacts);
  • A difference between two JsonValues
    //The source object
    JsonValue source = ... ;
    // The modified object
    JsonValue target = ... ;
    // The diff between source and target in a Json Merge Patch format
    JsonMergePatch mergePatch = Json.createMergeDiff(source,target);

See JSON Merge Patch RFC.

If you selected to install the Server Examples, the JSON P examples are located in the ORACLE_HOME\wlserver\samples\server\examples\src\examples\javaee8\jsonp directory, where ORACLE_HOME represents the directory in which you installed WebLogic Server.