Besides binding and converting JSON from an to Java objects with JSON-B, the Java EE specification (now Jakarta EE) offers a specification to process JSON data: JSON Processing (JSON-P). With this spec, you can easily create, write, read, stream, transform and query JSON objects. This specification is also part of the Eclipse MicroProfile project and provides a simple API to handle and further process JSON data structures as you'll see it in the following examples.
Learn more about the JSON Processing (JSON-P) specification and how to use it in this blog post.
Specification profile: JSON Processing (JSON-P)
- Current version: 1.1
- GitHub repository
- Specification homepage
- Basic use case: Process JSON messages (parse, generate, transform and query)
Construct JSON objects using JSON-P
With JSON-P you can easily build JSON objects on-demand. You can create a JsonObjectBuilder
using the Json
class and build the JSON object while adding new attributes to the object:
1 2 3 4 5 6 7 8 9 10 11 12 13 | JsonObject json = Json.createObjectBuilder() .add("name", "Duke") .add("age", 42) .add("skills", Json.createArrayBuilder() .add("Java SE") .add("Java EE").build()) .add("address", Json.createObjectBuilder() .add("street", "Mainstreet") .add("city", "Jakarta") .build()) .build(); |
If you print this object, you already have a valid JSON and can return this e.g. from a JAX-RS endpoint or use it as an HTTP request body:
1 | {"name":"Duke","age":42,"skills":["Java SE","Java EE"],"address":{"street":"Mainstreet","city":"Jakarta"}} |
You are not limited to create JSON objects only, you can also request for a JsonArrayBuilder
and start constructing your JSON array:
1 2 3 4 5 | JsonArray jsonArray = Json.createArrayBuilder() .add("foo") .add("bar") .add("duke") .build(); |
Write JSON objects
Given a JSON object, you can also write it to a different source using JSON-P and its JsonWriterFactory
. As an example, I'm writing a JSON object to a File
in pretty-print:
1 2 3 4 5 6 7 8 9 10 11 | private void prettyPrintJsonToFile(JsonObject json) throws IOException { Map<String,Boolean> config = new HashMap<>(); config.put(JsonGenerator.PRETTY_PRINTING, true); JsonWriterFactory writerFactory = Json.createWriterFactory(config); try (OutputStream outputStream = new FileOutputStream(new File("/tmp/output.json")); JsonWriter jsonWriter = writerFactory.createWriter(outputStream)) { jsonWriter.write(json); } } |
The JsonWriterFactory
accepts any Writer
or OutputStream
to instantiate the JsonWriter
:
1 2 3 4 5 6 7 8 9 10 11 | private void prettyPrintJsonToConsole(JsonObject json) throws IOException { Map<String,Boolean> config = new HashMap<>(); config.put(JsonGenerator.PRETTY_PRINTING, true); JsonWriterFactory writerFactory = Json.createWriterFactory(config); try (Writer stringWriter = new StringWriter(); JsonWriter jsonWriter = writerFactory.createWriter(stringWriter)) { jsonWriter.write(json); System.out.println(stringWriter); } } |
Using the JSON object from the chapter above, the output on the console will look like the following:
1 2 3 4 5 6 7 8 9 10 11 12 | { "name": "Duke", "age": 42, "skills": [ "Java SE", "Java EE" ], "address": { "street": "Mainstreet", "city": "Jakarta" } } |
Read JSON with the JSON-P specification
The specification also provides a convenient way to read and parse JSON from a given source (e.g. File
or String
). To create a JsonReader
instance, you either have to provide a InputStream
or a Reader
. As an example, I'm reading from both a String
and a File
on the classpath:
1 2 3 4 5 6 7 8 9 10 11 12 13 | private void readFromString() { JsonReader jsonReader = Json.createReader( new StringReader("{\"name\":\"duke\",\"age\":42,\"skills\":[\"Java SE\", \"Java EE\"]}")); JsonObject jsonObject = jsonReader.readObject(); System.out.println(jsonObject); } private void readFromFile() { JsonReader jsonReader = Json.createReader(this.getClass().getClassLoader() .getResourceAsStream("books.json")); JsonArray jsonArray = jsonReader.readArray(); System.out.println(jsonArray); } |
If the JSON is not valid, the JsonReader
throws a JsonParsingExcpetion
while parsing it and will give a hint about what is wrong e.g. Invalid token=SQUARECLOSE at (line no=1, column no=54, offset=53). Expected tokens are: [COLON]
.
Stream JSON data
For use cases where you have to process big JSON objects (which might not fit into memory), you should have a look at the streaming options of JSON-P. The specification says the following about its streaming capabilities:
Unlike the Object model this offers more generic access to JSON strings that may change more often with attributes added or similar structural changes. Streaming API is also the preferred method for very large JSON strings that could take more memory reading them altogether through the Object model API.
Streaming works for both parsing and generating JSON objects. To parse and process a big JSON object, the spec provides the JsonParser
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | String jsonString = "{\"name\":\"duke\",\"isRetired\":false,\"age\":42,\"skills\":[\"Java SE\", \"Java EE\"]}"; try (JsonParser parser = Json.createParser(new StringReader(jsonString))) { while (parser.hasNext()) { final Event event = parser.next(); switch (event) { case START_ARRAY: System.out.println("Start of array"); break; case END_ARRAY: System.out.println("End of array"); break; case KEY_NAME: System.out.println("Key found " + parser.getString()); break; case VALUE_STRING: System.out.println("Value found " + parser.getString()); break; case VALUE_NUMBER: System.out.println("Number found " + parser.getLong()); break; case VALUE_TRUE: System.out.println(true); break; case VALUE_FALSE: System.out.println(false); break; } } } |
This offers rather low-level access to the JSON object and you can access all Event
objects (e.g. START_ARRAY
, KEY_NAME
, VALUE_STRING
) while parsing.
For creating a JSON object in a streaming-fashion, you can use the JsonGenerator
class and write to any source using a Writer
or OutputStream
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | StringWriter stringWriter = new StringWriter(); try (JsonGenerator jsonGenerator = Json.createGenerator(stringWriter)) { jsonGenerator.writeStartArray() .writeStartObject() .write("name", "duke") .writeEnd() .writeStartObject() .write("name", "jakarta") .writeEnd() .writeEnd(); jsonGenerator.flush(); } System.out.println(stringWriter.toString()); |
Transform JSON with JsonPointer, JsonPatch and JsonMergePatch
Since JSON-P 1.1, the specification offers a great way to query and transform JSON structures using the following standardized JSON operations:
- JSON Pointer (official RFC)
- JSON Patch (official RFC)
- JSON MergePatch (official RFC)
Identify a specific value with JSON Pointer
If your JSON object contains several sub-objects and arrays and you have to find the value of a specific attribute, iterating over the whole object is cumbersome. With JSON Pointer you can specify an expression and point to a specific attribute and directly access it. The expression is defined in the official RFC.
Once you have a JSON pointer in place, you can get the value, remove it, replace it, add a new and check for existence with JSON-P and its JsonPointer
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | String jsonString = "{\"name\":\"duke\",\"age\":42,\"skills\":[\"Java SE\", \"Java EE\"]}"; JsonObject jsonObject = Json.createReader(new StringReader(jsonString)).readObject(); JsonPointer arrayElementPointer = Json.createPointer("/skills/1"); JsonPointer agePointer = Json.createPointer("/age"); JsonPointer namePointer = Json.createPointer("/name"); JsonPointer addressPointer = Json.createPointer("/address"); JsonPointer tagsPointer = Json.createPointer("/tags"); System.out.println("Get array element with pointer: " + arrayElementPointer.getValue(jsonObject).toString()); System.out.println("Remove age with pointer: " + agePointer.remove(jsonObject)); System.out.println("Replace name with pointer: " + namePointer.replace(jsonObject, Json.createValue("john"))); System.out.println("Check address with pointer: " + addressPointer.containsValue(jsonObject)); System.out.println("Add tags with pointer: " + tagsPointer.add(jsonObject, Json.createArrayBuilder().add("nice").build())); |
Define a sequence of operations to apply using JSON Patch
Similar to the JSON Pointer in the example above, you can define a set of operations to apply on a given JSON with JSON Patch. The possible operations to apply to a JSON are defined in the official RFC. As an example, I'm modifying an existing JSON with JsonPatch
like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 | String jsonString = "{\"name\":\"duke\",\"age\":42,\"skills\":[\"Java SE\", \"Java EE\"]}"; JsonObject jsonObject = Json.createReader(new StringReader(jsonString)).readObject(); JsonPatch patch = Json.createPatchBuilder() .add("/isRetired", false) .add("/skills/2", "Jakarta EE") .remove("/age") .replace("/name", "duke two") .build(); JsonObject patchedJson = patch.apply(jsonObject); System.out.println("Patched JSON: " + patchedJson); |
The patched JSON object looks like the following:
1 | Patched JSON: {"name":"duke two","skills":["Java SE","Java EE","Jakarta EE"],"isRetired":false} |
Merge two JSON objects with JSON Merge Patch
If you want to merge a given JSON object with another JSON, you can make use of the JSON Merge Patch. With this, you first have to define how the merge JSON object looks like and can then apply it to a target JSON structure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | String jsonString = "{\"name\":\"duke\",\"age\":42,\"skills\":[\"Java SE\", \"Java EE\"]}"; JsonObject jsonObject = Json.createReader(new StringReader(jsonString)).readObject(); JsonObject merge = Json.createObjectBuilder() .add("name", "duke2") .add("isEmployee", true) .add("skills", Json.createArrayBuilder() .add("CSS") .add("HTML") .add("JavaScript") .build()) .build(); JsonMergePatch mergePatch = Json.createMergePatch(merge); JsonValue mergedJson = mergePatch.apply(jsonObject); System.out.println("Merged JSON: " + mergedJson); |
The merged JSON in this example looks like the following:
1 | Merged JSON: {"name":"duke2","age":42,"skills":["CSS","HTML","JavaScript"],"isEmployee":true} |
For more information about the JSON Merge Patch, have a look at the official RFC.
YouTube video for using JSON-P 1.1 specification
Watch the following YouTube video of my Getting started with Eclipse MicroProfile series to see JSON-P 1.1 in action:
You can find the source code with further instructions to run this example on GitHub.
Have fun using the JSON-P specification,
Phil