Casual JSON generation with Jackson
aka POJOs are optional
As most Java developers know, Jackson is a very powerful library for converting between Plain Old Java Objects (POJOs) and various data formats, most notably JSON. By defining Java object — and especially with optional annotations — you can construct elaborate data structures to match almost any use case.
There is quite a bit of documentation showing how to do that; for example:
- Baeldung’s Jackson ObjectMapper tutorial
- Jenkov’s Jackson ObjectMapper
- Tutorials Point’s Jackson databinding
But while useful and often convenient, you do not really HAVE TO define Java classes write or read JSON with Jackson: for “casual” usage there are simple alternatives. Let’s have a look at such POJO-less approach.
Use Collections, Maps, for writing JSON
If you need to generate simplest of JSON messages for, say, unit tests, you can typically just construct Maps and Collections (or arrays). Starting with JDK helper classes, we can do:
ObjectMapper mapper = new JsonMapper(); // needed for all useString json = mapper.writeValueAsString(Collections.singletonMap(
"message", "Hello, world!"); // {"message":"Hello, world!"}
String json2 = mapper.writeValueAsString(Arrays.asList(true,
137, "stuff"); // [true,137,stuff];
// can combine as well
String json3 = mapper.writeValueAsString(Collections.singletonMap(
"props", Collections.singletonMap("id", 37)));
// {"props":{"id":37}}
So you can just create Map
s and Collection
s/arrays with names/values that you want and write them out.
For more convenience — at least for Map case (for JSON Arrays, Arrays.asList()
is fine), you may want to use either Guava (if on Java 8), or Java 9 Map.of(...)
for multiple entries case:
String json = mapper.writeValueAsString(ImmutableMap.of(
"id", 3,
"name", "Bob",
"props", ImmutableMap.of(
"type" : "A-25",
"rate" : 37.25
)
));
// {"id":3,"name":"Bob","props":{"type":"A-25","rate":37.25}}
and so forth. You can nest Map/Collection/array values easily as well.
Example above used just simple scalar types (numbers, Strings, booleans).
Actually, not just Collection/Map/array types
But wait! You are not really limited to simple types at all. Jackson can serialize all kinds of other types too. For example, binary values, enums, date/time values are available:
byte[] imageData = Encoder.encode(image); // from some external src
String json = mapper.writeValueAsString(Map.of(
"data" : imageData, // binary data encoded as base64
"type" : ImageType.JPEG, // enums handled just fine
"createdAt" : ZonedDateTime.now(), // as long as Java 8 date/time module registered
));
// {"data":"WRabGC=","type":"JPEG","createdAt":"2021-01-25T18:00:01.0003Z"}
but even more, you can mix and match:
- POJOs you might have
JsonNode
(tree model values)- Even pre-encoded JSON (with
RawValue
)
The last one might be of interest sometimes, when embedding already encoded JSON:
String innerJson = mapper.writeValueAsString(
Collections.singletonMap("id", "A-137"));
String combined = mapper.writeValueAsString(Arrays.asList(
"properties", new RawValue(innerJson));
// -> ["properties",{"id":"A-137"}]
Still quite configurable, too
Even with “casual” usage, you may want to change some aspects; for example, you may want to “pretty print” output. That works by using ObjectWriter
:
String prettyJson = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(Map.of("msg", "hello!"));
and similarly you can use alternate settings for various SerializationFeature
s, StreamWriteFeature
s, JsonWriteFeature
s and so on:
// Write date/time values as Strings, not numeric timestamps;
// also force escaping (quoting) of non-ASCII characters:
mapper.writer()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.enable(JsonWriteFeature.ESCAPE_NON_ASCII)
.writeValueAsString(...);
so simple does not mean limited.
May also consider Tree Model (JsonNode)
Another approach that works well for slightly bigger use cases — especially ones where functionality is split across different components — is to use Jackson’s Tree model:
ObjectNode root = mapper.createObjectNode();
root.put("message", "Hello, world!");
root.put("id", 137);
String json = mapper.writeValueAsString();
// Or, if default settings are ok, these work too
String json2 = root.toString();
String prettyJson = root.toPrettyString();
Also related: Jackson-jr
And finally, aside from simple JSON generation with “full” Jackson, there is also another possibly even more convenient way — jackson-jr’s “Composer” style, see: “Jackson jr for casual…”.
I will probably write a bit more about Composer-style soon, given that Jackson jr is still not as widely known as Jackson itself.