Jackson and JSON in Java, finally learn with a coding-first approach:
>> Download the eBookMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Introduction
In this article, weβll give an overview of the Optional class, and then explain some problems that we might run into when using it with Jackson.
Following this, weβll introduce a solution which will get Jackson to treat Optionals as if they were ordinary nullable objects.
2. Problem Overview
First, letβs take a look at what happens when we try to serialize and deserialize Optionals with Jackson.
2.1. Maven Dependency
To use Jackson, letβs make sure weβre using its latest version:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.17.2</version>
</dependency>
2.2. Our Book Object
Then, letβs create a class Book, containing one ordinary and one Optional field:
public class Book {
String title;
Optional<String> subTitle;
// getters and setters omitted
}
Keep in mind that Optionals should not be used as fields and we are doing this to illustrate the problem.
2.3. Serialization
Now, letβs instantiate a Book:
Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));
And finally, letβs try serializing it using a Jackson ObjectMapper:
String result = mapper.writeValueAsString(book);
Weβll see that the output of the Optional field, does not contain its value, but instead a nested JSON object with a field called present:
{"title":"Oliver Twist","subTitle":{"present":true}}
Although this may look strange, itβs actually what we should expect.
In this case, isPresent() is a public getter on the Optional class. This means it will be serialized with a value of true or false, depending on whether it is empty or not. This is Jacksonβs default serialization behavior.
If we think about it, what we want is for actual the value of the subtitle field to be serialized.
2.4. Deserialization
Now, letβs reverse our previous example, this time trying to deserialize an object into an Optional. Weβll see that now we get a JsonMappingException:
@Test(expected = JsonMappingException.class)
public void givenFieldWithValue_whenDeserializing_thenThrowException
String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": \"foo\" }";
Book result = mapper.readValue(bookJson, Book.class);
}
Letβs view the stack trace:
com.fasterxml.jackson.databind.JsonMappingException:
Can not construct instance of java.util.Optional:
no String-argument constructor/factory method to deserialize from String value ('The Parish Boy's Progress')
This behavior again makes sense. Essentially, Jackson needs a constructor which can take the value of subtitle as an argument. This is not the case with our Optional field.
3. Solution
What we want, is for Jackson to treat an empty Optional as null, and to treat a present Optional as a field representing its value.
Fortunately, this problem has been solved for us. Jackson has a set of modules that deal with JDK 8 datatypes, including Optional.
3.1. Maven Dependency and Registration
First, letβs add the latest version as a Maven dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.13.3</version>
</dependency>
Now, all we need to do is register the module with our ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
3.2. Serialization
Now, letβs test it. If we try and serialize our Book object again, weβll see that there is now a subtitle, as opposed to a nested JSON:
Book book = new Book();
book.setTitle("Oliver Twist");
book.setSubTitle(Optional.of("The Parish Boy's Progress"));
String serializedBook = mapper.writeValueAsString(book);
assertThat(from(serializedBook).getString("subTitle"))
.isEqualTo("The Parish Boy's Progress");
If we try serializing an empty book, it will be stored as null:
book.setSubTitle(Optional.empty());
String serializedBook = mapper.writeValueAsString(book);
assertThat(from(serializedBook).getString("subTitle")).isNull();
3.3. Deserialization
Now, letβs repeat our tests for deserialization. If we reread our Book, weβll see that we no longer get a JsonMappingException:
Book newBook = mapper.readValue(result, Book.class);
assertThat(newBook.getSubTitle()).isEqualTo(Optional.of("The Parish Boy's Progress"));
Finally, letβs repeat the test again, this time with null. Weβll see that yet again we donβt get a JsonMappingException, and in fact, have an empty Optional:
assertThat(newBook.getSubTitle()).isEqualTo(Optional.empty());
4. Conclusion
Weβve shown how to get around this problem by leveraging the JDK 8 DataTypes module, demonstrating how it enables Jackson to treat an empty Optional as null, and a present Optional as an ordinary field.
