Three ways to use Jackson for JSON in Java
Time to read: 7 minutes
If you’re working in a statically-typed language like Java then dealing with JSON can be tricky. JSON doesn’t have type definitions and is lacking some features which we would like - there’s only strings, numbers, booleans and null
, so to store other types (like dates or times) we’re forced to use a string-based convention. Despite its shortcomings, JSON is the most common format for APIs on the web so we need a way to work with it in Java.
Jackson is one of the most popular Java JSON libraries, and is the one I use most frequently. In this post I’ll pick a fairly complex JSON document and three queries which I want to make using Jackson. I’ll compare three different approaches:
- Tree model
- Data binding
- Path queries
All the code used in this post is in this repository. It’ll work with Java 8 onwards.
Other Java Libraries for working with JSON
The most popular Java libraries for working with JSON, as measured by usage in maven central and GitHub stars, are Jackson and Gson. In this post I will be using Jackson, and there is an equivalent post with Gson code examples here.
You can see the Jackson dependency for the examples here.
Example data and questions
To find some example data I read Tilde’s recent post 7 cool APIs you didn’t know you needed, and picked out the Near Earth Object Web Service API from the NASA APIs. This API is maintained by the brilliantly-named SpaceRocks team.
The NeoWS Feed API request returns a list of all asteroids whose closest approach to Earth is within the next 7 days. I’ll be showing how answer the following questions in Java:
- How many are there?
This can be found by looking at theelement_count
key at the root of the JSON object. - How many of them are potentially hazardous?
We need to loop through each NEO and check theis_potentially_hazardous_asteroid
key, which is a boolean value in the JSON. (Spoiler: it’s not zero 😨) - What is the name and speed of the fastest Near Earth Object?
Again, we need to loop through but this time the objects are more complex. We also need to be aware that speeds are stored as strings not numbers, eg"kilometers_per_second": "6.076659807"
. This is common in JSON documents as it avoids precision issues on very small or very large numbers.
A tree model for JSON
Jackson allows you to read JSON into a tree model: Java objects that represent JSON objects, arrays and values. These objects are called things like JsonNode
or JsonArray
and are provided by Jackson.
Pros:
- You will not need to create any extra classes of your own
- Jackson can do some implicit and explicit type coercions for you
Cons:
- Your code that works with Jackson’s tree model objects can be verbose
- It’s very tempting to mix Jackson code with application logic which can make reading and testing your code hard
Jackson tree model examples
Jackson uses a class called ObjectMapper
as its main entrypoint. Typically I create a new ObjectMapper
at application startup and because ObjectMapper instances are thread-safe it’s OK to treat it like a singleton.
An ObjectMapper
can read JSON from a variety of sources using an overloaded readTree
method. In this case I provided a String. readTree
returns a JsonNode
which represents the root of the JSON document. JsonNode
instances can be JsonObjects
, JsonArrays
or a variety of “value” nodes such as TextNode
or IntNode
.
Here is the code to parse a String of JSON into a JsonNode
:
[this code in the example repo]
How many NEOs are there?
We need to find the element_count
key in the JsonNode, and return it as an int
. The code reads quite naturally:
How many potentially hazardous asteroids are there this week?
I admit that I expected the answer here to be zero. It’s currently 19 - but I’m not panicking (yet). To calculate this from the root JsonNode
we need to:
- iterate through all the NEOs - there is a list of these for each date so we will need a nested loop
- increment a counter if the
is_potentially_hazardous_asteroid
field istrue
Here’s the code:
[this code in the example repo]
This is a little awkward - Jackson does not directly allow us to use the Streams API so I’m using nested for loops. asBoolean
returns the value for boolean fields in the JSON but can also be called on other types:
- numeric nodes will resolve as
true
if nonzero - text nodes are
true
if the value is"true"
.
What is the name and speed of the fastest NEO?
The method of finding and iterating through each NEO is the same as the previous example, but each NEO has the speed nested a few levels deep so you need to descend through to pick out the kilometers_per_second
value.
I created a small class to hold both values called NeoNameAndSpeed
. This could be a record
in the future. The code creates one of those objects like this:
[this code in the example repo]
Even though the speeds are stored as strings in the JSON I could call .asDouble()
- Jackson is smart enough to call Double.parseDouble
for me.
Data binding JSON to custom classes
If you have more complex queries of your data, or you need to create objects from JSON that you can pass to other code, the tree model isn’t a good fit. Jackson offers another mode of operation called data binding, where JSON is parsed directly into objects of your design. By default Spring MVC uses Jackson in this way when you accept or return objects from your web controllers.
Pros:
- JSON to object conversion is straightforward
- reading values out of the objects can use any Java API
- the objects are independent of Jackson so can be used in other contexts
- the mapping is customizable using Jackson Modules
Cons:
- Up-front work: you have to create classes whose structure matches the JSON objects, then have Jackson read your JSON into these objects.
Data binding 101
Here’s a simple example based off a small subset of the NEO JSON:
We could imagine a class for holding that data like this:
Jackson is almost able to map back and forth between JSON and matching objects like this out of the box. It copes fine with the int id
actually being a string, but needs some help converting the String 2020-04-12
to a LocalDate
object. This is done with a custom module, which defines a mapping from JSON to custom object types.
Jackson data binding - custom types
For the LocalDate
mapping Jackson provides a dependency. Add this to your project and configure your ObjectMapper
like this:
[this code in the example repo]
Jackson data binding - custom field names
You might have noticed that I used closeApproachDate
in my example JSON above, where the data from NASA has close_approach_date
. I did that because Jackson will use Java’s reflection capabilities to match JSON keys to Java field names, and they need to match exactly.
Most times you can’t change your JSON - it’s usually coming from an API that you don’t control - but you still wouldn’t like to have fields in your Java classes written in snake_case
. This could have been done with an annotation on the closeApproachDate
field:
[this code in the example repo]
Creating your custom objects with JsonSchema2Pojo
Right now you are probably thinking that this can get very time-consuming. Field renaming, custom readers and writers, not to mention the sheer number of classes you might need to create. Well, you’re right! But fear not, there’s a great tool to create the classes for you.
JsonSchema2Pojo can take a JSON schema or (more usefully) a JSON document and generate matching classes for you. It knows about Jackson annotations, and has tons of options, although the defaults are sensible. Usually I find it does 90% of the work for me, but the classes often need some finessing once they’re generated.
To use it for this project I removed all but one of the NEOs and selected the following options:
[generated code in the example repo]
Data stored in keys and values
The NeoWS JSON has a slightly awkward (but not unusual) feature - some data is stored in the keys rather than the values of the JSON objects. The near_earth_objects
map has keys which are dates. This is a bit of a problem because the dates won’t always be the same, but of course jsonschema2pojo doesn’t know this. It created a field called _20200412
. To fix this I renamed the class _20200412
to NeoDetails
and the type of nearEarthObjects
became Map<String, List<NeoDetails>>
(see that here). I could then delete the now-unused NearEarthObjects
class.
I also changed the types of numbers-in-strings from String
to double
and added LocalDate
where appropriate.
Jackson data binding for the Near-Earth Object API
With the classes generated by JsonSchema2Pojo the whole big JSON document can be read with:
Finding the data we want
Now that we have plain old Java objects we can use field access and the Streams API to find the data we want:
[this code in the example repo]
This code is more natural Java, and it doesn’t have Jackson all through it so it would be easier to unit test this version. If you are working with the same format of JSON a lot, the investment of creating classes is likely to be well worth it.
Path queries from JSON using JsonPointer
With Jackson you can also use a JSON Pointer which is a compact way to refer to a specific single value in a JSON document:
[this code in the example repo]
JSON Pointers can only point to a single value - you can’t aggregate or use wildcards, so they are rather limited.
Summing up the different ways of using Jackson
For simple queries, the tree model can serve you well but you will most likely be mixing up JSON parsing and application logic which can make it hard to test and maintain.
To pull out a single value from a JSON document, you might consider using a JSON pointer, but the code is barely any simpler than using the tree model so I never do.
For more complex queries, and especially when your JSON parsing is part of a larger application, I recommend data binding. It’s usually easiest in the long run, remembering that JsonSchema2Pojo can do most of the work for you.
What are your favourite ways of working with JSON in Java? Let me know on Twitter I’m @MaximumGilliard, or by email I am mgilliard@twilio.com. I can’t wait to see what {"you": "build"}
.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.