0

First Look at JSR 374, JSON-P 1.1 EDR

The first early draft of JSON-P 1.1 (i.e. JSON-P 1.1 EDR1) released on the 3rd of August, will be covered in this first-look-article. As you probably know, Java API for JSON Processing 1.0 (JSR 353) is the standard JSON Processing API added in Java EE 7. Now, JSON-P is being updated with the new version JSON-P 1.1 also known as JSR 374. The current build comes with new JSON-based features such as JSON Patch, JSON Pointer, JSON Merge Patch, and Java SE 8 support, each of which has been scheculed so as to be part of Java EE 8. We will briefly document about the current noteworthy unites along with small examples in order that, you can try the draft version by yourselves and/or get some ideas about the new implemented functionalities.

In examining JSON-P 1.1 API, we have found 5 noteworthy classes, each of which is located in the javax.json package as follows:

  1. JsonUtil

  2. JsonPointer

  3. JsonPatch

  4. JsonMergePatch

  5. stream.JsonCollectors

javax.json.JsonUtil

The JsonUtil class is an utility class which has been available in 1.1 API. It contains a class method called toJson that accepts a JSON String as the method parameter. This static member manipulates the specified JSON String in order to compute and return the corresponding JSON value.

JsonValue users = JsonUtil.toJson(            (1)
        "[                                 " +
        "  { 'name': 'Ali',                " +
        "    'age': 25,                    " +
        "    'gender': 'M',                " +
        "    'hobbies': [                  " +
        "       'hiking', 'graffiti']},    " +
        "  { 'name': 'Veli',               " +
        "    'age': 23,                    " +
        "    'gender': 'M',                " +
        "    'hobbies': [                  " +
        "       'hiking']},                " +
        "  { 'name': 'Ayşe',               " +
        "    'age': 26,                    " +
        "    'gender': 'F',                " +
        "    'hobbies': [                  " +
        "       'driving', 'fishing']}     " +
        " ]");

JsonArray array = (JsonArray) users; (2)
1 A simple usage of the class method with the provided JSON string.
2 Since the given JSON structure is a JSON array, we can cast it to JsonArray.

javax.json.JsonPointer

JSON Pointer is an IETF RFC 6901 specification. JSON-P 1.1 provides a concrete immutable representation module called JsonPointer, for this specification. The JsonPointer class lets you retrieve a specific value within a target JSON document by means of a pointer signature. A JSON pointer syntax is specified in Unicode characters consisting of a sequence of zero or more reference tokens. Each pointer must start with a ‘/’ character unless you want to obtain the whole JSON document with an empty string as a pointer (e.g. "").

JsonObject object = (JsonObject)JsonUtil.toJson(
    "   {                             " +
    "      'foo': ['bar', 'baz'],     " + (1)
    "      '': 0,                     " + (2)
    "      'a/b': 1,                  " + (3)
    "      'c%d': 2,                  " + (4)
    "      'e^f': 3,                  " + (5)
    "      'g|h': 4,                  " +
    "      'i\\\\j': 5,               " + (6)
    "      'k\\\"l': 6,               " + (7)
    "      ' ': 7,                    " +
    "      'm~n': 8,                  " + (8)
    "      's': [ {                   " +
    "                't':'u'          " + (9)
    "             }                   " +
    "       ]                         " +
    "   }");

JsonPointer pointer  = new JsonPointer("\ ");  (10)
JsonValue result = pointer.getValue(object);   (11)
1 3 different pointers can be defined for the foo array. To obtain the whole foo array, the pointer must be "/foo". If you want to receive the first element (i.e. 'bar') of the foo, you need the "/foo/0" composition. In addition, you are able to retrieve the second element using the "/foo/1" pointer.
2 To find the value, 0, "\" is the pointer for ''.
3 ‘/’ needs to be encoded as ‘~1’ when the character appears in a reference token. In this case, our pointer should be "/a~1b".
4 There is no any encoding conversion for such key-value pairs. "/c%d" is just enough pointer string.
5 "/e^f" simply gets the value 3 for us.
6 "/i\\j" lets us find the value which is 5.
7 The pointer signature of 'k\"l' is "/k\"l".
8 ‘~’ needs to be encoded as ‘~0’, so our pointer will be "/m~0n".
9 To reach the first element {'t':'u'} of s, the signature must be defined as "/s/0"; however, our point is to retrieve the value 'u' of 't'. We therefore use the following signature "/s/0/t".
10 JsonPointer defines a single user-defined constructor which accepts a JSON pointer string. In our case, the signature is "/ " where the ' ': 7 pair is referred.
11 Using the getValue method, we find the value of the key.

Alternatively, you can add a JSON value, replace a JSON value with another value, or remove the JSON value in the specified JSON structure using the following overloaded methods of the JsonPointer class:

javax.json.JsonPointer#add(javax.json.JsonStructure, javax.json.JsonValue)
javax.json.JsonPointer#add(javax.json.JsonObject, javax.json.JsonValue)
javax.json.JsonPointer#add(javax.json.JsonArray, javax.json.JsonValue)
javax.json.JsonPointer#replace(javax.json.JsonStructure, javax.json.JsonValue)
javax.json.JsonPointer#replace(javax.json.JsonObject, javax.json.JsonValue)
javax.json.JsonPointer#replace(javax.json.JsonArray, javax.json.JsonValue)
javax.json.JsonPointer#remove(javax.json.JsonStructure)
javax.json.JsonPointer#remove(javax.json.JsonObject)
javax.json.JsonPointer#remove(javax.json.JsonArray)

For instance, here is a code snippet showing the add operation in terms of a given JSON string:

An example of the add operation of the JsonPointer class
JsonObject targetObj = (JsonObject)JsonUtil.toJson(
        "  {                             " +
        "    'name': 'Ali',              " +
        "    'age': 25,                  " +
        "    'hobbies': [                " +
        "       'hiking', 'graffiti'     " +
        "     ]                          " +
        "  }");

JsonObject op = (JsonObject) JsonUtil.toJson("{'pointer':'/gender','value':'M'}");
JsonPointer pointer = new JsonPointer(op.getString("pointer"));
JsonObject modified = (JsonObject) pointer.add(targetObj, op.get("value"));
the modified object will shape as:
{
    'name': 'Ali',
    'age': 25,
    'hobbies': [
       'hiking', 'graffiti'
     ]
     'gender': 'M'
}

javax.json.JsonPatch

Another IETF RFC 6902 specification has been adapted in JSON-P 1.1. JSON Patch works with a JSON array which must contain one or more specific patch operation(s). There are 6 kind of Patch operations namely; add, remove, replace, move, copy, and test. In giving a set of operation(s), JSON Patch applies to a target JSON document. The class, JsonPatch, embodies the ability of the specification in Java.

There are two ways that you can make use of JsonPatch
  1. Instantiate JsonPatch with a JsonArray.

  2. Apply to JsonPatchBuilder.

First approach:
JsonArray patch = (JsonArray) JsonUtil.toJson(
        "[" +                                                           (1)
        "{ 'op': 'copy', 'path': '/nickname', 'from': '/name' },    " + (2)
        "{ 'op': 'remove', 'path': '/hobbies/1' },                  " + (3)
        "{ 'op': 'add', 'path': '/hobbies/-', 'value': 'cycling' }  " + (4)
        "]");

JsonObject targetObj = (JsonObject) JsonUtil.toJson(
        "  {                             " +
        "    'name': 'Ali',              " +
        "    'nickname': '',             " +
        "    'age': 25,                  " +
        "    'hobbies': [                " +
        "       'hiking', 'graffiti'     " +
        "     ]                          " +
        "  }");

JsonPatch patch = new JsonPatch(patch); (5)
JsonStructure result = patch.apply(targetObj);(6)
1 Each JSON Patch operation (i.e. object) must be defined in a JSON array which is generally called as JSON Patch document. The current JSON Patch document is composed of 3 JSON Patch objects, all of which are applied to our target JSON document sequentially.
2 Our first applied operation is the copy operation. This operation is in charge of copying a value at a specified location to the target location. The operation must contain a from member which is a type of JSON string indicating a JSON Pointer value. The operation should copy the value ,Ali, of the name member to our target member’s value (nickname).
3 The next operation in the order is the remove operation. Only two main operation members (op and path) must be included for the operation to be successful. This op will remove the second element (i.e. graffiti) of hobbies.
4 The last patch object is about the add operation in the JSON Patch document. This object must contain a value member whose content specifies the value to be added. In our case, the value pointing a JSON string which is cycling, is added to the end of the array. The JSON pointer, /hobbies/-, refers to the last index of hobbies.
5 We simply create a new JsonPatch object with the JSON Patch document.
6 Specified patch operations are applied to our target JSON document and then we get the resulting JSON object.
We strongly encourage you to look at the rest operations of JSON Patch!
Each operation object must contain only one op member which indicates the operation value. Each operation object must contain only one path member whose value must be a string containing a JSON-Pointer signature. The ordering of members of a JSON Patch object does not have a privilege.
A) The transformed JSON object:
{
    "name":"Ali",
    "nickname":"Ali",
    "age":25,
    "hobbies":[
        "hiking","cycling"
    ]
}

You can also perform the previous scenario using the JsonPatchBuilder class.

Second approach:
JsonObject targetObj = (JsonObject) JsonUtil.toJson(
        "  {                             " +
        "    'name': 'Ali',              " +
        "    'nickname': '',             " +
        "    'age': 25,                  " +
        "    'hobbies': [                " +
        "       'hiking', 'graffiti'     " +
        "     ]                          " +
        "  }");

JsonPatchBuilder patchBuilder = new JsonPatchBuilder();
JsonObject result = patchBuilder
        .copy("/nickname", "/name")
        .remove("/hobbies/1")
        .add("/hobbies/-", "cycling")
        .apply(targetObj);
B) The result is the same as A:
{
    "name":"Ali",
    "nickname":"Ali",
    "age":25,
    "hobbies":[
        "hiking","cycling"
    ]
}

javax.json.JsonMergePatch

The implementation of JSON Merge Patch (RFC 7386) being new standard in the JSON space, has been added to the JSON-P 1.1 API. The JsonMergePatch class has two class methods. While the mergePatch static method applies the specified patch to the specified target, the diff method generates a JSON Merge Patch from the JSON source object and JSON target object.

An example of the usage of mergePatch:
JsonValue target = JsonUtil.toJson(            (1)
        "  {                             " +
        "    'name': 'Ali',              " +
        "    'nickname': '',             " +
        "    'age': 25,                  " +
        "    'hobbies': [                " +
        "       'hiking', 'graffiti'     " +
        "     ]                          " +
        "  }");

JsonValue patch =  JsonUtil.toJson(             (2)
        "  {                             " +
        "    'nickname': null,           " +
        "    'age': 30,                  " +
        "    'hobbies': [                " +
        "       'cycling'                " +
        "     ],                         " +
        "    'email': 'ali@email.com'    " +
        "  }");

JsonValue result = JsonMergePatch.mergePatch(target, patch); (3)
1 Our target JSON resource.
2 Our merge patch document is responsible for comparing the merge patch with the current content of the target resource to determine the specific set of change operations to be applied to the target object. What this patch tells is that we want to replace the value of the age with 30, remove the nickname member, add a new email member, replace the hobbies array, and leave the name member untouched.
3 The static method ,mergePatch, has two parameters: the target JSON document and the merge patch document. If the patch is not a JSON object, the result will be replaced the entire target with the entire patch. After applying the patch to the target object, you should see a new JSON object applied a set of operations.
C) The result returning from the mergePatch:
{
    "name":"Ali",
    "age":30,
    "hobbies":[
        "cycling"
    ],
    "email":"ali@email.com"
}
Table 1. Showing the resulting values of the combination of target and patch
Target Patch Merge Result

{“a”: [
{“b”:”c”} ] }

{“a”: [1]}

{“a”: [1]}

{“a”:”b”}

{“a”:”c”}

{“a”:”c”}

{“a”:”b”}

{“b”:”c”}

{“a”:”b”,

{“a”:”b”}

{“a”:null}

{}

{“a”:”b”,
“b”:”c”}

{“a”:null}

{“b”:”c”}

{“a”:[“b”]}

{“a”:”c”}

{“a”:”c”}

{“a”:”c”}

{“a”:[“b”]}

{“a”:[“b”]}

{“a”: {
“b”: “c”}
}

{“a”: {
“b”: “d”,
“c”: null}
}

{“a”: {
“b”: “d”}
}

An example of the usage of diff:
JsonValue target =  JsonUtil.toJson(
        "  {                             " +
        "    'name': 'Ali',              " +
        "    'nickname': '',             " +
        "    'age': 25,                  " +
        "    'hobbies': [                " +
        "       'hiking', 'graffiti'     " +
        "     ]                          " +
        "  }");
JsonValue source = JsonUtil.toJson(
        "{                            " +
        " 'name':'Ali',               " +
        " 'age':30,                   " +
        " 'hobbies':[                 " +
        "   'cycling'                 " +
        "  ],                         " +
        "  'email':'ali@email.com'    " +
        "}");
JsonValue result = JsonMergePatch.diff(source, target); (1)
1 The result will show that there are a few differences between the source and the target. These differences point the actual JSON merge path. If we call the mergePatch method with the returned JSON merge patch and the source object, the current target object would be obtained.
D) The result of diff:
{
    "age":25,
    "hobbies":[
        "hiking","graffiti"
    ],
    "email":null,
    "nickname":""
}

javax.json.stream.JsonCollectors

Like Collectors, JsonCollectors utility class has some usefull methods that can create a Collector which will accumulate the input JsonValue elements into a mutable result container that can be a JsonArray or JsonObject. We need to use the collect method to terminate the stream along with JsonCollectors. There are four utility methods, as follows:

  • toJsonArray():

    • Adds the JsonValue elements of the stream into a JsonArray

  • toJsonObject(Function key, Function value):

    • Adds the JsonValue elements of the stream into a JsonObject, created using key and value.

  • groupingBy(Function classifier):

    • Groups JsonValue stream elements using the Function classifier into a JsonObject.

  • groupingBy(Function classifier, Collector downstream):

    • Groups JsonValue stream elements using the Function classifier into a JsonObject.

    • A reduction is performed in each group using Collector.

JsonArray users = (JsonArray)JsonUtil.toJson(
        "[                                 " +
        "  { 'name': 'Ali',                " +
        "    'age': 25,                    " +
        "    'gender': 'M',                " +
        "    'hobbies': [                  " +
        "       'hiking', 'graffiti']},    " +
        "  { 'name': 'Veli',               " +
        "    'age': 23,                    " +
        "    'gender': 'M',                " +
        "    'hobbies': [                  " +
        "       'hiking']},                " +
        "  { 'name': 'Ayşe',               " +
        "    'age': 26,                    " +
        "    'gender': 'F',                " +
        "    'hobbies': [                  " +
        "       'driving', 'fishing']}     " +
        " ]");

The next 3 examples illustrate how we can use the available methods in the light of the user array respectively.

E1) Find the users where the age is greater than or equal to 25 and then add their name into a JsonArray:

toJsonArray()
JsonArray result = users.getValuesAs(JsonObject.class).stream()
            .filter(x-> x.getInt("age") >= 25)
            .map(x -> x.get("name"))
            .collect(JsonCollectors.toJsonArray());
the result:
['Ali','Ayşe']

E2) Add name-hobbies pairs into a JsonObject:

toJsonObject(…​):
JsonObject result = users.getValuesAs(JsonObject.class).stream()
            .collect(JsonCollectors.toJsonObject(
                    x-> x.asJsonObject().getString("name"),
                    x-> x.asJsonObject().getJsonArray("hobbies")));
the result:
{
    "Ali"  : ["hiking","graffiti"],
    "Veli" : ["hiking"],
    "Ayşe" : ["driving","fishing"]
}

E3) Group users who like driving or hiking:

groupingBy(…​)
JsonObject result = users.getValuesAs(JsonObject.class).stream()
    .collect(
    JsonCollectors.groupingBy(x ->
      ((JsonObject)x).getJsonArray("hobbies").getJsonString(0).getString()));
the result being:
{
    "driving":[
        {"name":"Ayşe","age":26,"gender":"F","hobbies":["driving","fishing"]}
    ],
    "hiking":[
        {"name":"Ali","age":25,"gender":"M","hobbies":["hiking","graffiti"]},
        {"name":"Veli","age":23,"gender":"M","hobbies":["hiking"]}
    ]
}

Since, It is a draft, the API will still evolve and feedback also is so valuable. Istanbul JUG attempts to give intelligible spec suggestions. What’s more, you can be one of us; just read the Javadoc and try the API to provide feedback and do not forget to append the [Adopt-a-JSR] tag in the subject of your e-mail, and say I’m [name] from Istanbul JUG.

Hakan Özler

MongoDB and Docker Enthusiast, JCP Contributor. Java and Certified MongoDB developer. Computer Engineer, MSc.

Leave a Reply