We had a REST API call that was causing a lot of overhead for a client application due to the number of requests it needed to make. We wanted to add a new REST API call that could return multiple items in a single request, however we did not want to cause memory pressure on the server by loading everything into memory first, then returning it. Instead we wanted to be able to stream the JSON results to an output stream as each item was read. For this task we decided to use the Jackson JsonGenerator.
You can download the example project from GitHub here.
If you are using Maven, you can add the Jackson dependency to your pom.xml file:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.1</version>
</dependency>
The following class PrintOutputStreamRunnable is just to show the current state of the OutputStream as we write data to it using JsonGenerator.
import java.io.ByteArrayOutputStream;
public class PrintOutputStreamRunnable implements Runnable {
private final ByteArrayOutputStream outputStream;
private boolean isRunning;
public PrintOutputStreamRunnable(ByteArrayOutputStream outputStream) {
this.outputStream = outputStream;
this.isRunning = true;
}
@Override
public void run() {
try {
String result = "";
while (isRunning) {
if (!result.equals(outputStream.toString())) {
result = outputStream.toString();
System.out.println(result);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void stop() {
this.isRunning = false;
}
}
Now the following class JsonGeneratorExample has our JsonGenerator example code. Note how after adding each chunk of JSON we call the flush() command to flush the JSON in the buffer to the OutputStream.
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import java.io.ByteArrayOutputStream;
public class JsonGeneratorExample {
public static void main(String[] args) throws Exception {
// Create the OutputStream we will be writing JSON to
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Create a background thread to print out the contents of the OutputStream
PrintOutputStreamRunnable runnable = new PrintOutputStreamRunnable(outputStream);
Thread thread = new Thread(runnable);
thread.start();
// Create the JsonGenerator to write to our OutputStream
JsonFactory factory = new JsonFactory();
JsonGenerator generator = factory.createGenerator(outputStream, JsonEncoding.UTF8);
generator.writeStartObject();
generator.writeArrayFieldStart("cars");
generator.writeStartObject();
generator.writeStringField("make", "Mercedes");
generator.writeStringField("model", "C300");
generator.writeNumberField("doors", 4);
generator.writeEndObject();
generator.flush(); // Flush buffered JSON to the output stream
Thread.sleep(1000);
generator.writeStartObject();
generator.writeStringField("make", "Ford");
generator.writeStringField("model", "Focus");
generator.writeNumberField("doors", 2);
generator.writeEndObject();
generator.flush(); // Flush buffered JSON to the output stream
Thread.sleep(1000);
generator.writeStartObject();
generator.writeStringField("make", "Infiniti");
generator.writeStringField("model", "G35");
generator.writeNumberField("doors", 4);
generator.writeEndObject();
generator.flush(); // Flush buffered JSON to the output stream
Thread.sleep(1000);
generator.writeEndArray();
generator.writeEndObject();
generator.close();
outputStream.close();
runnable.stop();
}
}
If you are using Maven from the command line you can run the following commands:
- mvn compile
- mvn exec:java -Dexec.mainClass=”JsonGeneratorExample”
The output here shows the state of the OutputStream after each chunk of JSON is flushed:
{"cars":[{"make":"Mercedes","model":"C300","doors":4}
{"cars":[{"make":"Mercedes","model":"C300","doors":4},{"make":"Ford","model":"Focus","doors":2}
{"cars":[{"make":"Mercedes","model":"C300","doors":4},{"make":"Ford","model":"Focus","doors":2},{"make":"Infiniti","model":"G35","doors":4}
{"cars":[{"make":"Mercedes","model":"C300","doors":4},{"make":"Ford","model":"Focus","doors":2},{"make":"Infiniti","model":"G35","doors":4}]}
As you can see in the output, each time we call the flush() command the JSON in the buffer is written to the OutputStream.
I hope that helps!