One of the additions of Java EE 7 was the JSON-P specification for processing JSON documents. But even now, the JSON-P spec is moving forward, and for Java EE 8, it will come with a lot of new features like support for JSON Pointer (RFC6901), JSON Patch (RFC6902) or JSON Merge Patch (RFC7386). Moreover, there will be improvements to performance, Java 8 integration, and methodology for processing big JSON data.

This post presents the updates that are coming to the Java API for JSON Processing which will be added to the Java EE 8 spec, as well as how other Java EE 8 JSRs are going to align with it.

Introduction to JSON-Processing API 1.0

JSON-Processing API was a part of Java EE 7 under the JSR-353 spec. This spec contains the basic API for parsing and writing JSON files in Java using Object model API or Stream approach.

It offers basic classes like javax.json.Json, java.json.JsonReader, java.json.JsonWriter, and javax.json.JsonString.

A basic example of reading and writing a JSON file using model object approach in spec might be:

JsonReader reader = Json.createReader(new FileReader(“books.json”));
JsonStructure  books = reader.read();

Where JsonStructure is the parent object of JsonObject or JsonArray types, you can understand a JsonArray as a list of JsonObjects, and a JsonObject as a Map of attributes. Depending on the content of the file, read() method will return a JsonObject or a JsonArray.

For creating a JSON object, you can use createObjectBuilder to create builder object for constructing new JSON documents:

JsonObject book = Json.createObjectBuilder()
   .add("title", "Snow White")
   .add("year", 1812)
  .build();

What’s new in JSON-Processing API 1.1

Following the basis of JSON-Processing API 1.0 and reusing same elements, JSON-Processing API 1.1 has been improved in the following areas:

Update to RFC 7159

JSON-Processing API 1.0 was based on RFC 4627. JSON-Processing API 1.1 has been updated to RFC 7159. The major change is that now the root of a JSON document might contain a simple value instead of only JSON objects or JSON arrays. For this reason javax.java.Json, javax.java.JsonReader and javax.java.JsonWriter has been updated with following methods:

Json class:

public static JsonString createValue(String value)
public static JsonNumber createValue(int value)

Similarly for long, double, BigInteger, and BigDecimal

JsonReader:

default JsonValue readValue()

JsonWriter:

default void write(JsonValue value)

You can generate, read or write a simple value to be compliant with RFC 71559.

JsonArray and JsonObject transformations

In JSON-Processing API 1.0, JsonObject and JsonArray are immutable objects. This means that once created, it can’t be modified. So if you want to change a JSON element you should do a manual copy each of the properties of the JSON object into JsonObjectBuilder/JsonArrayBuilder and then modify the object.

With JSON-Processing API 1.1, these objects are still immutable, but a JsonObjectBuilder/JsonArrayBuilder can be created from a given JsonObject/JsonArray so the copy of the properties are done automatically.

// Create the target
JsonArray target = Json.createArrayBuilder().add(…).build();

// Create a builder initialized with the target values
JsonArrayBuilder builder = Json.createArrayBuilder(target);

// Operations on the array
JsonArray result = builder.add(99, "john")
                          .set(100, 1234)
                          .remove(3)
                          .build();

JSON Pointer

Apart from modifying existing code, JSON-Processing API 1.1 also adds an implementation of JSON Pointer (RFC 6901).

JSON Pointer defines a string syntax for identifying a specific value inside a JSON document.

Given document:

{
  "author":  {
    "name":"Duke"
   }
}

If you want to point to name field, you should use next syntax:

/author/name

In case of arrays, you can use an integer to point to an element of the array and it is 0 based.

For example:

/books/0

It would be a pointer to first element of the array.

It is important to notice that a JSON Pointer expression must be an absolute path or an exception is thrown.

To get a JSON Pointer in JSON-Processing API, you need to use the new JsonPointer class:

JsonStructure author = reader.read();

JsonPointer p = new JsonPointer("/author/name");
JsonValue name = p.getValue(author);

Hwoever, JSON-Processing API 1.1 also offers some operations that are not described in RFC but might prove useful too. These operations are conceived to apply modifications to the elements pointed by the expression.  These operations are:

  • add(JsonStructure, JsonValue)
  • replace(JsonStructure, JsonValue)
  • remove(JsonStructure)

For example, to change the author of previous JSON document you could do:

JsonStructure author = reader.read();

JsonPointer p = new JsonPointer("/author/name");
JsonStructure newAuthor = p.replace(author, Json.createValue(“NewDuke“));

And in the same way for adding and removing operations.

JSON Patch

When developing a REST API, you usually need to implement CRUD operations. The following HTTP Method headers are used for each case:

  • Create: POST
  • Retrieve: GET
  • Update: PUT
  • Delete: DELETE

The major problem is in the update operation. First PUT HTTP Method in a REST API means create or update, so you need to implement both operations (it is not a pure update in this sense). And second, you need to define a format on how a JSON document is updated. For example you might need to define if you send all the document or only the pieces that are changed, or if nullifying a field means removing that field or nullify this field.

To avoid having to take these kind of decisions and also to make an update standard and reusable, JSON-Processing API 1.1 implements JSON Patch (RFC 6902) specification.

JSON Patch is a RFC specification that standardise the update operation. JSON Patch is a JSON document that contains a sequence of modifications, which they are executed all of them or none of them. It has an HTTP Method which is PATCH and application/json-patch+json media type is used to identify such documents.

The sequence of modifications that JSON Patch support are test, remove, add, replace, move and copy.

Let’s see an example of JSON Patch document.

Given document:

{
  "title":"Guinness",
    "brewery": {
      "key": "guinness"
    }
}

If you apply this JSON Patch document:

[
  {"op":"replace", "path":"/brewery/key", “value”:"GBrewery"},
  {"op":"remove", "path": "/title"}
]

Notice that a JSON Patch document is composed by an array. op field which is used to set the operation to apply. The path is a JSON Pointer expression that points to the element which must receive the operation, and in the case of replacing, you need to set the value you want to set.

The resulting document will be:

{
  "brewery": {
    "key": "GBrewery"
  }
}

For applying the JSON Patch document into a JSON document with JSON-Processing API there are two methods:

The first one is using a JsonArray class and creating the JsonObject from scratch:

// JSON Array with JSON Objects containing JSON Patch fields
JsonArray patchExpression = Json.createArrayBuilder().add(Json.createObjectBuilder().add(…))
                   .add(…);
JsonPatch jsonPatch = new JsonPatch(patchExpression);

JsonStructure beer = reader.read();

// Finally the patch is applied to a JSON document
JsonStructure newBeer = jsonPatch.apply(beer);

The second one is using JsonPatchBuilder which is a class added in JSON-Processing API 1.1 for guide you through the creation of a patch document:

JsonStructure beer = reader.read();
JsonPatchBuilder patchBuilder = new JsonPatchBuilder();

// Construct and apply the JSON Patch
JsonStructure newBeer = patchBuilder.replace("/brewery/key", “GBrewery")
                             .remove("/title")
                                    .apply(beer)

JSON-Processing API 1.1 implements one extra operation which is not specified in the RFC. This operation is calculates the difference between two JSON documents. You can understand as the reverse operation of JSON Patch. Given two JSON documents it returns the JSON Patch document that should be applied to go from document A to document B.

For example:

JsonArray diff = JsonPatch.diff(beer, newBeer2);

Returns a JSON Path document:

[
  {"op":"replace", "path":"/brewery/key", “value”:"GBrewery"},
  {"op":"remove", "path": "/title"}
]

JSON Merge Patch

For updating a JSON document there is another specification called JSON Merge Patch (RFC 7386). It standardises the update operation, but instead of sending a new JSON document with the operations to apply, you send a document which its syntax mimics the document being modified. You only send the fields with the updates. The special value null is used to indicate that a field should be removed. As in JSON Patch, PATCH  is its HTTP Method and application/json-patch+json media type is used to identify such documents.

Let’s see an example of JSON Merge Patch.

Given document:

{
  "title":"Guinness",
    "brewery": {
      "key": "guinness"
      }
}

Creating JSON Merge Patch document to:

{"title":null}

After applying previous JSON Merge Patch to JSON document, the new JSON document looks like:

"brewery": {
    "key": "guinness"
}

Notice that the title has been removed.

If you want to add the country of origin, you could create next JSON Merge Patch:

{"origin":"Ireland"}

For using JSON Merge Patch in JSON-Processing API you need to use JsonMergePatch class:

JsonStructure author = reader.read();

JsonPointer p = new JsonPointer("/author/name");
JsonValue name = p.getValue(author);

And as in JSON Patch implementation, JSON-Processing API takes one step further JSON Merge Patch to offer an extra operation for getting the difference between two JSON documents.

If you want to use this feature you need to use JsonMergePatch as well:

JsonValue diff = JsonMergePatch.diff(beer, newBeer);

which returns this JSON document:

{"title":null}

JSON Queries

The minimum requirement for Java EE 8 is going to be Java 8. Since a JsonObject is a Map and JsonArray is a list, JSON-Processing API takes advantage of Java’s stream operations using Lambda expressions. The only element that has been developed is a Collector that returns JsonArray or JsonObject instead of List or Map.

Let’s see some examples.

Suppose this is the next JSON document:

[                                 
        { "name": "Duke",               
           "age": 18,                    
           "gender": "M",               
           "phones": {                   
              "home": "650-123-4567",    
              "mobile": “650-234-5678"}},

         { "name": "Janev",               
          "age": 23,                    
          "gender": "F",                
          "phones": {                   
             'mobile': “707-999-5555"}},

        { "name": "Joanna",             
          "gender": "F",                
          "phones": {                   
              "mobile": "505-333-4444"}} 
]

You can get a new JsonObject with only name and mobile phone of people of gender F.

JsonObject result = contacts.getValuesAs(JsonObject.class).stream()
                    .filter(x->"F".equals(x.getString("gender")))
                    .collect(JsonCollectors.toJsonObject(
                            x->x.asJsonObject().getString("name"),
                            x->x.asJsonObject()
                        .getJsonObject("phones").get("mobile"));

Executing this code returns a JsonObject with the following elements:

{
    "Jane":"707-999-5555",
    "Joanna":"505-333-4444"
}

Notice that first argument of JsonCollectors.toJsonObject() is the key of the new document that in this case is the name, and second argument is the value which is the phone number.

If you wanted to return the JSON document in its original form you could call JsonCollectors.toJsonArray() collector.

And of course any other streams operations such as map or peek can be used.

Big JSON Data

The final element that has been added at the time of writing this post is processing of Big JSON Data.

As you have read in this post, when you load a JSON document into JsonObject or JsonArray, the whole document is loaded into memory. This might cause some memory problems when trying to process big JSON documents or read JSON data that is dynamically generated. For this reason, you can use JsonParser class which parsers JSON using streaming model. It is efficient in execution and memory usage and it works at token level.

JSON-Processing API offers two methods, skipArray which skips tokens and advance the parser to END_ARRAY, or skipObject which skips tokens and advance the parser to END_OBJECT.

Let’s suppose you have a really big JSON file which is composed by an array of JSON objects. Each JSON object is the description of an issue and may contains some fields like title, author, description and the state among others. Now you need to print 5 issues with state open. Notice that you don’t need to read the whole array of issues but only reading one by one until you print 5 issues containing the state open.

With JSON-Processing API you could do it with this code:

JsonParser parser = Json.createParser(inputStream);

while (parser.hasNext()) {
    if (parser.next() == JsonParser.Event.START_ARRAY) {
        parser.getArrayStream()
         // Turn into Stream<JsonObject>
            .map(v->v.asJsonObject())
            .filter(obj->obj.getString("state").equals("open"))
            .limit(5)
            .forEach(obj->System.out.println(
                         "issue number: " + obj.getInt("number") + 'n' +
                         "title: " + obj.getString("title") + 'n' +
                         “=================")
          );
                
             // Skip the rest of the JsonArray
             parser.skipArray();
      }
} 

Observe that you are using JsonParser object and we move the parser to first Array (which in our case is the root element). Next we get an array stream and JsonValue is transformed into JsonObject with map call, issues are filtered, and finally only the 5 first parsed are shown.

Although now you are dealing with low-level API, the code is still pretty clear and more important with low-memory requirements.

Alignment with JSON-B and JAX-RS

WARNING: anything exposed inside this section might be changed in the future. Information provided here is valid at the time of writing this post.

JSON-B

JSON-B gives an API to marshall and unmarshal Java objects to/from JSON. It comes from the best practices of existing JSON binders like Gson, Jackson or Johnzon.

It only offers a common ground for implementations but it is NOT a new JSON binder.

For example:

Jsonb jsonb = JsonbBuilder.create();



// Unmarshal

Book book = jsonb.fromJson(new File(“book.json”), Book.class);

// Marshal

jsonb.toJson(book, new File(“book.json”));

JSON-B supports binding of elements of JSON Processing API like JsonObject and JsonArray,

so JSON-P and JSON-B are aligned in this sense.

JAX-RS

JAX-RS spec is going to align to JSON Processing API, by providing to developers @Patch annotation for setting HTTP method to PATCH. Also, MediaType class is going to add application/json-patch+json Mime Type. Finally, in case endpoint payload contains a JSON-PATCH document, developers might be able to add JsonPatch object as method parameter for automatic marshalling.

@Patch
@Consumes(MediaType.APPLICATION_JSON_PATCH)
public Response updateBook(..., JsonPatch patch){}

JSON Processing API current status

JSON Processing API is almost finished, although we haven’t discarded new additions or discussed with everyone who wants to collaborate on the addition of new features.

The early draft review can be found here.

At the time of writing this article, JSON-Processing API RI has not been released in any artefact repository, but you can download the code and build it on your own. You can find all the resources at https://jsonp.java.net/.