FasterXML/jackson Tips for JSON in Java

Kamer Elciyar
Mar 6, 2020 · 7 min read

Hello, in this article I will try to lead you to go beyond default Jackson usage in Spring Boot applications. My main resource will be official documentation as always. Source codes and additional references can be found at the end of the article.

Introduction

All my examples will be in a Spring Boot project that has Web and Lombok dependencies. I will not list all features and explain them one by one like a documentation. Instead, I will ask some questions and answer them. I hope you will find your questions! If not so, you can ask me on or .

Q1: How to create a Java POJO for any JSON schema?
Q2: How to use different field names in POJO to map JSON?
Q3: How to deserialize nested JSON objects?
Q4: How to create a custom deserializer?
Q5: How to deserialize enum values?

What is Jackson?

This article is not a zero-to-hero-like article. So, I will not start from basics but introduce Jackson with a few words. Because you may have been using Jackson library without knowing you do so. Jackson is the most widely used JSON processing library for Java. It has 3 core modules (jackson-core, jackson-annotations, jackson-databind), third-party modules for different integrations. You may have been using them because spring-boot-starter-web includes these three modules(and more) and Jackson is registered as default object mapper library. This is why you can produce JSON data by just returning Java object in your controller in your Spring application.

Q1: How to create a Java POJO for any JSON input?

Perhaps, this is the simplest question that comes to mind. Let’s see an example JSON:

{
"title": "2019-2020 Algorithms Final Exam",
"lecture": "Algorithms",
"examDate": "2020-03-04T09:00:00.000Z",
"studentNumber": 75,
"questions" : [
{
"questionNumber": 1,
"question": "What is an asymptotic notation?"
},
{
"questionNumber": 2,
"questions": "Find worst case for insertion sort?"
},
{
"questionNumber": 3,
"question": "Which algorithm would you choose to sort 1m numbers, why?"
}
]
}

Let’s analyze this JSON. This is an object that has title (string), lecture (string), examDate (date), studentNumber (numeric value, int is the best option for this case.) and questions (array) fields. So, we should map this JSON to a Java object that has the same fields. All fields are OK but questions array has an object type that has questionNumber(numberic value, int) and question (string) fields.

First of all, create a Question object as below:

@Getter
@Setter
@ToString
class Question {
private Integer questionNumber; private String question;}

Then, create Exam object.

@Getter
@Setter
@ToString
public class Exam {
private String title; private String lecture; private LocalDateTime examDate; private Integer studentNumber; private List<Question> questions;}

That’s all. Finally, create a controller.

@PostMapping("/question-one")
ResponseEntity<Void> questionOne(@RequestBody Exam exam) {
log.info("Parsed object: {}", exam);
return ResponseEntity.ok().build();
}

Now, send JSON with any method you like. Here’s a cURL request and you will see the desired output.

curl --location --request POST '<http://localhost:8080/question-one>' \\
--header 'Content-Type: application/json' \\
--data-raw '{
"title": "2019-2020 Algorithms Final Exam",
"lecture": "Algorithms",
"examDate": "2020-03-04T09:00:00.000Z",
"studentNumber": 75,
"questions" : [
{
"questionNumber": 1,
"question": "What is an asymptotic notation?"
},
{
"questionNumber": 2,
"question": "Find worst case for insertion sort?"
},
{
"questionNumber": 3,
"question": "Which algorithm would you choose to sort 1m numbers, why?"
}
]
}'

We mapped JSON array as List in our POJO. This is not a must, we can also use Java Array (Question[]), Collection<>, Set<>, Iterable<> and any other type that implements Iterable<>.

If you don’t need all of the values in JSON you can omit them in your POJO.

Q2: How to use different field names in POJO to map JSON?

In Q1 we named our fields according to given JSON. But this is not a must. We can use different names with many different options. But I will explain only two of them. So let’s create an example JSON:

{
"productTitle": "TWSBI ECO Fountain Pen White M Nib",
"productPrice": "30.00",
"productCategoryId": 13,
"productTagId": 235
}

As you see, there is a JSON object -let’s call it product- and it has 4 fields. Creating a POJO with the same field names are possible. But removing product... prefix is better.

Option 1:

Create a POJO as below:

@Getter
@Setter
@ToString
public class ProductOptionOne {
@JsonAlias("productTitle")
private String title;
@JsonAlias("productPrice")
private String price;
@JsonAlias("productCategoryId")
private Integer categoryId;
@JsonAlias("productTagId")
private Integer tagId;
}

@JsonAlias annotation makes possible to define multiple options for a property. So @JsonAlias("productTitle") annotation catches productTitle key in JSON. But don't forget, this annotation creates alternatives only. Actual property name doesn't change.

This option works perfectly but not the best option. Because what we want is to change the property name not to create alternatives.

Option 2:

@JsonProperty annotation is the other option. This will change the property name:

@Getter
@Setter
@ToString
public class ProductOptionTwo {
@JsonProperty("productTitle")
private String title;
@JsonProperty("productPrice")
private String price;
@JsonProperty("productCategoryId")
private Integer categoryId;
@JsonProperty("productTagId")
private Integer tagId;
}

Also: We can use these two annotations at the same time. @JsonProperty will change the actual name and @JsonAlias will add other options.

Also: You can set an access option on @JsonProperty. This will provide you some options such as changing the property name on deserialization/serialization/both.

Q3: How to deserialize nested JSON objects?

In a standard JSON communication, you are likely to encounter nested objects inside JSON. In Q1 we deserialized a JSON input with multiple classes. But there are other options.

{
"title": "TWSBI ECO Fountain Pen White M Nib",
"price": "30.00",
"taxonomy": {
"categoryId": 13,
"tagId": 235
}
}

Option 1:

First option is to map nested object to a Map. This is very easy.

@Getter
@Setter
@ToString
public class Q3ProductOptionOne {
private String title; private Float price; private Map<String, String> taxonomy;}

Jackson will initialize this map with categoryId and tagId keys.

Option 2

@Getter
@Setter
@ToString
public class Q3ProductOptionTwo {
private String title; private Float price; private Integer tagId; private Integer categoryId; @JsonProperty("taxonomy")
@SuppressWarnings("unchecked")
private void taxonomyDeserializer(Map<String, Object> taxonomy) {
this.tagId = (Integer) taxonomy.get("tagId");
this.categoryId = (Integer) taxonomy.get("categoryId");
}
}

This option looks like the previous option. But it’s better I think. Because we does not use map class as nested object. Instead, we give this nested map to a private method annotated with @JsonProperty and @SuppressWarnings. Then extract values from map and set to fields.

Option 3

This option is a different question. So, check Q4 for this.

Q4: How to create a custom deserializer?

There may be some cases that you don’t want to use annotations or these annotations are not enough for your development. So, you can create your own deserializer that you receive raw JSON and export POJO, then, register it to overwrite default deserializer. Let’s create a JSON that has nested objects.

{
"applicant": {
"name": "Joss",
"surname": "Stone"
},
"application": {
"announcementId": 2,
"givenInput": {
"profileLink": "<https://dummypage.com>",
"cvFileName": "myAwesomeCv.pdf"
}
}
}

We can deserialize it as it is explained in Q3. But creating a custom deserializer is another option.

This is our POJO:

@Getter
@Setter
@Builder
@ToString
public class Application {
private String applicationFullName; private Integer announcementId; private String applicantProfileLink; private String cvName;
}

And this is deserializer:

public class ApplicationDeserializer extends StdDeserializer<Application> {	public ApplicationDeserializer() {
this(null);
}
public ApplicationDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Application deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
final JsonNode jsonNode = jp.getCodec().readTree(jp);
final JsonNode applicantNode = jsonNode.get("applicant");
final JsonNode applicationNode = jsonNode.get("application");
final String name = applicantNode.get("name").asText();
final String surname = applicantNode.get("surname").asText();
final Integer announcementId = applicationNode.get("announcementId").asInt();
final JsonNode givenInputNode = applicationNode.get("givenInput");
final String profileLink = givenInputNode.get("profileLink").asText();
final String cvFileName = givenInputNode.get("cvFileName").asText();
return Application.builder()
.applicationFullName(name + " " + surname)
.announcementId(announcementId)
.applicantProfileLink(profileLink)
.cvName(cvFileName)
.build();
}
}

All elements are JsonNode in this deserialization process. If you chain a method such as asText(), asInt(), they are converted into desired form. Otherwise they are treated as JsonNode. You can perform all kinds of validations and manipulations also.

Lastly, let’s register deserializer class in our POJO class with the annotation below:

@JsonDeserialize(using = ApplicationDeserializer.class)

Q5: How to deserialize enum values?

Deserializing enum values is easy. You can do it without a tutorial. But there are different options. First of all, create an enum class.

public enum ContainerStatusOptionOne {	RUNNING, FAILED, PENDING, REMOVING
}

Then, create a POJO that has this enum as a field.

@Getter
@Setter
@ToString
public class Container {
private String containerTitle; private ContainerStatus containerStatus;}

Option 1:

This is option is the first that comes to mind. Just send the name of the enum value.

{
"containerTitle": "Stage",
"containerStatus": "RUNNING"
}

But don’t forget, this option works case sensitive. If you send running as a value, you get HttpMessageNotReadableException.

Option 2:

This option is as easy as the first option but personally, I don’t find it useful. When you send numeric values for enum, Jackson deserializes it also. RUNNING is 0 for our example and REMOVING is 3. So:

{
"containerTitle": "Stage",
"containerStatus": 0
}

is the same as the example above.

Option 3:

There are some situations that you may work with 3rd party services and you cannot change your request body according to your POJO. For instance, 3rd party service sends lowercase values for enums. Jackson provides an annotation for this also. We need to change enum values as below:

public enum ContainerStatusOptionTwo {	@JsonProperty("running") RUNNING,
@JsonProperty("failed") FAILED,
@JsonProperty("pending") PENDING,
@JsonProperty("removing") REMOVING
}

Now, Jackson will look for running, failed, pending and removing as input. So, sending the JSON below works:

{
"containerTitle": "Stage",
"containerStatus": "running"
}

References

Github Repo:

The Startup

Get smarter at building your thing. Join The Startup’s +789K followers.

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. 

By signing up, you will create a Medium account if you don’t already have one. Review our for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Kamer Elciyar

Written by

Java | Spring | https://kamer.dev kamer@kamer.dev

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +789K followers.

Kamer Elciyar

Written by

Java | Spring | https://kamer.dev kamer@kamer.dev

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +789K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface.

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox.

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store