Casual JSON generation with Jackson

aka POJOs are optional

@cowtowncoder
3 min readFeb 23, 2021

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:

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 Maps and Collections/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, StreamWriteFeatures, JsonWriteFeatures 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.

--

--

@cowtowncoder

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox