Jackson Tips: ObjectNode.putPOJO(), putRawValue() for fun and profit

aka “No Need to Convert from Map to JsonNode”

@cowtowncoder
4 min readSep 29, 2021

Jackson works with All Kinds of Java Types

Jackson library allows use of all kinds of Java object types to represent JSON data to read and write: from java.util.Maps, java.util.Collection s and arrays to “Plain Old Java Object” (POJOs, aka Beans), as well as most scalar JDK types (Strings, Numbers, Booleans, Date/Time values). But equally importantly Jackson provides its own “Tree Model”: a node-based representation where JSON values are represented by various sub-types of JsonNode base type — ObjectNode for JSON Objects, ArrayNode for JSON Arrays, TextNode for JSON Strings and so on.

Tree Model is a convenient representation for loosely structured data and for cases where only a small subset of content is of interest: you either can’t or don’t want to create a POJO representation of the structure. And while you could also use “natural” binding of Jackson — read JSON Objects as java.util.Map, JSON Arrays as java.util.List and so on— JsonNode is usually more convenient and safer to navigate (no explicit null checks; automatic type coercions).

Tree Model for Reading content

Typical usage of Tree Model for reading content like this:

{ "id" : 123,
"name" : "Bob",
"properties" : {
"value" : 15.0
}
}

looks something like:

ObjectMapper mapper = new JsonMapper();
JsonNode root = mapper.readTree(jsonContent);
double value = root.at("/properties/value").asDouble();

You can also conveniently convert between Java representations like so:

// assuming MyValue has compatible structure
MyValue value = mapper.treeToValue(root, MyValue.class);
// converting to Map fine as well:
Map<String, Object> map = mapper.treeToValue(root, Map.class);

so you are not bound to using JsonNode for all processing either.

Tree Model for generating content

But Tree Model is not just good for reading: it works fine for transformations (read + modify) and for generating content from other data sources.
You can construct content to serialize first as ObjectNode, then write as JSON:

ObjectNode obToWrite = mapper.createObjectNode();
obToWrite.put("id", 123);
obToWrite.put("name", "Bob");
// and so on... and then serialize
OutputStream out = ...;
mapper.writeValue(out, obToWrite);

So far there is nothing particularly new here.

But a lesser known approach is to use value types OTHER than JsonNode with node types, to combine different kinds of Java values with Tree Model.

Without ability (or rather, without knowing of it) to combine Tree Model with other types, you might need code like this (and something that is commonly used):

ObjectNode obToWrite = mapper.createObjectNode();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Integer) {
obToWrite.put(key, ((Integer) value).intValue();
} else {
// and so on for all content types
}
}

which will manually convert from Java Map into JsonNode, for purpose of using result just for writing JSON content.

But is this really necessary?

No-Conversion Tree Model usage: just add “POJO”s!

Turns out this is often not necessary, as long as value you have can be serialized by Jackson —which is true for Maps, for example, when values container are serializable (recursively).
After all, following is perfectly valid usage:

String json = mapper.writeValueAsString(map);

But how does this related to Tree Model? Turns out that in addition to specific typed “put()” (and “set()”) methods there is a bit more special method in ObjectNode: ObjectNode.putPOJO(). Although the name suggests it would only be used for actual POJOs (aka Beans) — that is, custom-defined Java value types with getters and/or public fields — it simply means something like “add this arbitrary Java value as value of specific property [to be serialized as it would outside of JsonNode]”. So you can do things like:

ObjectNode root = mapper.createObjectNode();
root.put("id", id);
Map<String, Object> props = createValueMap();
root.putPOJO("properties", props);
Person p = fetchPersonalInformation(in);
root.putPOJO("personal", p);
String json = mapper.writeValueAsString();

and serialization “just works”.
The opposite route is perfectly legit as well:

static class PojoWithNode {
public int id;
public JsonNode extraData;
}
PojoWithNode pwn = ...;
String json = mapper.writeValueAsString(pwn);

That is, you can freely mix and match various Java values and Jackson can figure out how to do the binding as expected. And this works for both reading and writing: as long as your Java content model (with whatever types you like) is structurally compatible with JSON (and other content Jackson supports) conversions “just work”.

It is worth noting that ArrayNode supports “POJO” additions as well, with ArrayNode.addPOJO().

Pre-serialized Content Inclusion with Tree Model

So far so good. But there is one more neat trick, this time just for serialization (writing content): you can even include formerly serialized (JSON) content.

Here’s a somewhat contrived example:

MyStuff reusableContent = ...;
RawValue raw =
new RawValue(mapper.writeValueAsString(reusableContent));
// ...
ObjectNode root = mapper.createObjectNode();
root.put("id", 358);
// and other stuff
// and then something that has already been serialized (or
// possibly received from somewhere and not deserialized at all)
root.putRawValue("other", raw);

What is happening there? Basically RawValue is a wrapper that is passed to underlying JsonGenerator and is indicate that textual content is to be embedded exactly as-is, assuming it is a valid JSON value (caller to verify — invalid content leads to invalid JSON written).

The main use for this for embedding segments of JSON that have been encoded ahead of time: usually for performance reasons (it is faster to just concatenate textual content without escaping and checks).
Note, however, that use of this embedding can have security ramifications: never embed content provided by untrusted sources as that is a well-known source of security holes.

--

--

@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