Processing model: Tree Model
Of 3 major processing modes that Jackson supports, Tree Model may be most familiar to developers with experience using XML as the main data/transfer format. At conceptual level it has many similarities to DOM XML tree model; although there are also many differences due to structural and semantic differences between JSON and XML.
Constructing Trees from JSON content
Trees are usually constructed using ObjectMapper, similar to how Data Binding is done. There are actually two distinct methods for doing this:
1 // general method, same as with data binding
2 ObjectMapper mapper = new ObjectMapper();
3 // (note: can also use more specific type, like ArrayNode or ObjectNode!)
4 JsonNode rootNode = mapper.readValue(src, JsonNode.class); // src can be a File, URL, InputStream etc
5
6 // or, with dedicated method; requires a JsonParser instance
7 JsonParser jp = ...; // construct using JsonFactory (can get one using ObjectMapper.getJsonFactory)
8 rootNode = mapper.readTree(jp);
9 // (most useful when binding sub-trees)
Traversing Tree Model
Basic JsonNode interface has most accessor methods (although none of methods to change tree). For basic traversal, there are separate methods for JSON Objects and Arrays -- Objects are indexed by property name, Arrays by element index. In addition, it is possible to use "safe" methods which will return dummy MissingNode instead of null if Object/Array does not contain indicated value. Methods available thus are:
JsonNode get(int index) : for Arrays, returns child element at 'index' (indexes are 0-based), if one exists
- If index is outside of array size (less than 0, greater or equal than array size), returns null
If node is not a ArrayNode, returns null
JSON Null values will be of type NullNode, NOT Java nulls (since null indicates value not existing)
JsonNode get(String property) : for Objects, returns value of property named 'property', if one exists
- If no such property exists, returns null
If node is not an ObjectNode, returns null
JSON Null values will be of type NullNode, NOT Java nulls (since null indicates value not existing)
JsonNode path(int index), JsonNode path(String property) : Similar to 'get(int)' and 'get(String)' methods; but instead of returning Java null for missing values, will return MissingNode
Benefit of MissingNode is that it implements all JsonNode methods as expected: never has any value, but can be further traversed (resulting always in a MissingNode).
- Very useful in safe traversal: if data is there, will be traversed; if not, will eventually result in missing data. This can be considered similar to SQL NULL value.
JsonNode with(int index) : Similar to 'path(String)' methods; but instead of returning MissingNode, will actually create and add new ObjectNode
- Very useful for safe modifications: you can "materialize" sub-trees as necessary
So considering following JSON document:
{
"array" : [ 1, { "name" : "Billy" }, null ],
"object" : { "id" : 123, "names" : [ "Bob", "Bobby" ] }
}as 'root' we could access information using methods like:
JsonNode array = root.get("array"); // would return null if none found
// let's use "path()" -- if no such element, will return MissingNode which can be dereferenced!
String name = array.path(1).getValueAsText(); // or, getTextValue if it's certain it is JSON String
String id = root.path("object").path("id").getTextValue(); // so it'll be null if not found
// or
JsonNode idNode = root.path("object").path("id"); // never returns null
if (idNode.isMissingNode()) {
throw new IllegalArgumentException("Could not find id!");
}
// or maybe we just want to traverse all values of properties of the array node?
for (JsonNode node : root.path("array")) {
System.out.println("Entry: "+node.toString());
}Even better, we could also do:
root.with("object").with("attrs").put("firstAttribute", 1);and tree being modified to look like:
{
"array" : [ 1, { "name" : "Billy" }, null ],
"object" : {
"id" : 123,
"names" : [ "Bob", "Bobby" ],
"attrs" : { "firstAttribute" : 1 }
}
}which is much more convenient than having to check for existence (or lack thereof) along the path.
Note that the main difference between path() and with() therefore is that former works well with read-access, and latter works better when modifying things.
TO BE COMPLETED
(what else should be added here?)
Constructing Tree Model instances from Scratch
It is also possible to construct tree instances without external JSON source.
For example:
1 ObjectMapper mapper = new ObjectMapper();
2 JsonNode rootNode = mapper.createObjectNode(); // will be of type ObjectNode
3 ((ObjectNode) rootNode).put("name", "Tatu");
4 // ... and so forth
Writing Trees as JSON
Writing out Trees is done exactly the same way as writing out regular Java objects as JSON; that is:
mapper.writeValue(dst, myBean); // dst can be File, OutputStream, Writer etc
Converting to Token Stream
(note: available since version 1.3)
Assuming you have a JsonNode, you can also create a JsonParser to traverse its contents as if it was a token stream] by:
JsonParser jp = node.traverse(); // or: JsonParser jp = mapper.treeAsTokens(node);
Converting to Java Objects
(note: available since version 1.3)
You can do conversion explicitly by first converting Tree to Token Stream (as above) and then binding; but there is a short-cut too:
MyBean bean = mapper.treeToValue(node, MyBean.class);
Additional Reading
Using Jackson Tree Model (Cowtowncoder Blog)
Traversing JSON trees with Jackson (Cowtowncoder Blog)
