This is part 3 of the Bleeding edge Java series. Start at the introduction if you haven’t already.
Printing is the process of converting a stream of JSON tokens into JSON text. For our library, our printer only needs to print a single JSON token at a time. Our serializer already produces a stream of JSON tokens and the JDK has a large set of existing methods for managing streams.
What we need is a mapping function that can be passed to the JDK Stream’s
map()
method. We can define a Java interface for this:
public interface JsonPrinter
{
CharSequence print(JsonToken jsonToken);
}
With an instance of this interface we can serialize from a Java object to a stream of JSON tokens to JSON text like this:
String jsonText = serializer.serialize(object)
.map(printer::print)
.collect(Collectors.joining());
The implementation for our printer is very straightforward. Note the code uses a StringUtils utility from the link at the end of this article:
CharSequence print(JsonToken jsonToken)
{
return switch (jsonToken) {
case NumberToken(var number) -> number.toString();
case StringToken(var string) -> StringUtils.quoteAndEscape(string);
case BooleanToken(var value) -> value ? "true" : "false";
case NullToken __ -> "null";
case BeginArrayToken __ -> "[";
case EndArrayToken __ -> "]";
case BeginObjectToken __ -> "{";
case EndObjectToken __ -> "}";
case ObjectNameToken(var name) -> StringUtils.quoteAndEscape(name) + ":";
case ValueSeparatorToken __ -> ",";
};
}
Here again we take advantage of enhanced switch and pattern matching (see
Serialization for more details).
Additionally, we use the deconstruction feature of Record Patterns.
With the first case statement, case NumberToken(var number)
, the compiler matches
if the token is a number token and then extracts the number token’s value, binding it to the new
variable number
. It can be thought of as this Java code:
if (jsonToken instanceOf NumberToken) {
var number = ((NumberToken) jsonToken).value();
}
Finally, notice that the switch
statement does not need a default
case. We
have case
statements for all possible implementations in the JsonToken sealed
hierarchy. The Java compiler knows this and doesn’t require a default
. If in
the future we add a new JsonToken type this code would no longer compile.
Test it out for yourself!
In the prior article we developed a serializer that can serialize a Java record into a stream of JSON tokens. We now have a printer that can map a JSON token into JSON text. Let’s put this together in jshell. The example use these files:
- TypeToken.java
- JsonToken.java
- JsonSerializer.java
- StringUtils.java (utility class)
- JsonPrinter.java
From a terminal with Java 19 installed, run the following (note you’ll need the wget utility):
wget -nc https://raw.githubusercontent.com/starburstdata/developer-blog-assets/main/bleeding-edge-java/code/TypeToken.java
wget -nc https://raw.githubusercontent.com/starburstdata/developer-blog-assets/main/bleeding-edge-java/code/JsonToken.java
wget -nc https://raw.githubusercontent.com/starburstdata/developer-blog-assets/main/bleeding-edge-java/code/JsonSerializer.java
wget -nc https://raw.githubusercontent.com/starburstdata/developer-blog-assets/main/bleeding-edge-java/code/StringUtils.java
wget -nc https://raw.githubusercontent.com/starburstdata/developer-blog-assets/main/bleeding-edge-java/code/JsonPrinter.java
jshell --enable-preview TypeToken.java JsonToken.java JsonSerializer.java StringUtils.java JsonPrinter.java
Inside jshell let’s serialize a Java record into JSON text:
var serializer = JsonSerializer.instance();
var printer = JsonPrinter.instance();
record Person(String name, int age) {}
var person = new Person("someone", 28);
String jsonText = serializer.serialize(person) // serialize record to stream of tokens
.map(printer::print) // map each JsonToken to a String (as a CharSequence)
.collect(Collectors.joining()); // collect into a String
System.out.println(jsonText);
Summary
We can now serialize a Java record into JSON text. Let’s look at parsing into tokens next in parsing.
We’re hiring
Want to be able to use the latest features of Java? We’re hiring!
Jordan Zimmerman is a Senior Software Engineer working on Starburst Galaxy.