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. Overview
This tutorial focuses on understanding the Jackson ObjectMapper class and how to serialize Java objects into JSON and deserialize JSON string into Java objects.
To understand more about the Jackson library in general, the Jackson Tutorial is a good place to start.
Further reading:
Inheritance with Jackson
Jackson JSON Views
Jackson - Custom Serializer
2. Dependencies
Letβs first add the following dependencies to the pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.3</version>
</dependency>
This dependency will also transitively add the following libraries to the classpath:
- jackson-annotations
- jackson-core
Always use the latest versions from the Maven central repository for jackson-databind.
3. Reading and Writing Using ObjectMapper
Letβs start with the basic read and write operations.
The simple readValue API of the ObjectMapper is a good entry point. We can use it to parse or deserialize JSON content into a Java object.
Also, on the writing side, we can use the writeValue API to serialize any Java object as JSON output.
Weβll use the following Car class with two fields as the object to serialize or deserialize throughout this article:
public class Car {
private String color;
private String type;
// standard getters setters
}
3.1. Java Object to JSON
Letβs see a first example of serializing a Java object into JSON using the writeValue method of the ObjectMapper class:
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow", "renault");
objectMapper.writeValue(new File("target/car.json"), car);
The output of the above in the file will be:
{"color":"yellow","type":"renault"}
The methods writeValueAsString and writeValueAsBytes of ObjectMapper class generate a JSON from a Java object and return the generated JSON as a string or as a byte array:
String carAsString = objectMapper.writeValueAsString(car);
3.2. JSON to Java Object
Below is a simple example of converting a JSON String to a Java object using the ObjectMapper class:
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Car car = objectMapper.readValue(json, Car.class);
The readValue() function also accepts other forms of input, such as a file containing JSON string:
Car car = objectMapper.readValue(new File("src/test/resources/json_car.json"), Car.class);
or an URL:
Car car =
objectMapper.readValue(new URL("file:src/test/resources/json_car.json"), Car.class);
3.3. JSON to Jackson JsonNode
Alternatively, a JSON can be parsed into a JsonNode object and used to retrieve data from a specific node:
String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
JsonNode jsonNode = objectMapper.readTree(json);
String color = jsonNode.get("color").asText();
// Output: color -> Black
3.4. Creating a Java List From a JSON Array String
We can parse a JSON in the form of an array into a Java object list using a TypeReference:
String jsonCarArray =
"[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});
3.5. Creating Java Map From JSON String
Similarly, we can parse a JSON into a Java Map:
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Map<String, Object> map
= objectMapper.readValue(json, new TypeReference<Map<String,Object>>(){});
4. Advanced Features
One of the greatest strengths of the Jackson library is the highly customizable serialization and deserialization process.
In this section, weβll go through some advanced features where the input or the output JSON response can be different from the object that generates or consumes the response.
4.1. Configuring Serialization or Deserialization Feature
While converting JSON objects to Java classes, in case the JSON string has some new fields, the default process will result in an exception:
String jsonString
= "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" : \"1970\" }";
The JSON string in the above example in the default parsing process to the Java object for the Class Car will result in the UnrecognizedPropertyException exception.
Through the configure method, we can extend the default process to ignore the new fields:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Car car = objectMapper.readValue(jsonString, Car.class);
JsonNode jsonNodeRoot = objectMapper.readTree(jsonString);
JsonNode jsonNodeYear = jsonNodeRoot.get("year");
String year = jsonNodeYear.asText();
Yet another option is based on the FAIL_ON_NULL_FOR_PRIMITIVES, which defines if the null values for primitive values are allowed:
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
Similarly, FAIL_ON_NUMBERS_FOR_ENUM controls if enum values are allowed to be serialized/deserialized as numbers:
objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false);
You can find the comprehensive list of serialization and deserialization features on the official site.
4.2. Creating Custom Serializer or Deserializer
Another essential feature of the ObjectMapper class is the ability to register a custom serializer and deserializer.
Custom serializers and deserializers are very useful in situations where the input or the output JSON response is different in structure than the Java class into which it must be serialized or deserialized.
Below is an example of a custom JSON serializer:
public class CustomCarSerializer extends StdSerializer<Car> {
public CustomCarSerializer() {
this(null);
}
public CustomCarSerializer(Class<Car> t) {
super(t);
}
@Override
public void serialize(
Car car, JsonGenerator jsonGenerator, SerializerProvider serializer) {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("car_brand", car.getType());
jsonGenerator.writeEndObject();
}
}
This custom serializer can be invoked like this:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module =
new SimpleModule("CustomCarSerializer", new Version(1, 0, 0, null, null, null));
module.addSerializer(Car.class, new CustomCarSerializer());
mapper.registerModule(module);
Car car = new Car("yellow", "renault");
String carJson = mapper.writeValueAsString(car);
Hereβs what the Car looks like (as JSON output) on the client side:
var carJson = {"car_brand":"renault"}
And hereβs an example of a custom JSON deserializer:
public class CustomCarDeserializer extends StdDeserializer<Car> {
public CustomCarDeserializer() {
this(null);
}
public CustomCarDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Car deserialize(JsonParser parser, DeserializationContext deserializer) {
Car car = new Car();
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
// try catch block
JsonNode colorNode = node.get("color");
String color = colorNode.asText();
car.setColor(color);
return car;
}
}
This custom deserializer can be invoked in this way:
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module =
new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(Car.class, new CustomCarDeserializer());
mapper.registerModule(module);
Car car = mapper.readValue(json, Car.class);
4.3. Handling Date Formats
The default serialization of java.util.Date produces a number, i.e., epoch timestamp (number of milliseconds since January 1, 1970, UTC). But this is not very human readable and requires further conversion to be displayed in a human-readable format.
Letβs wrap the Car instance we used so far inside the Request class with the datePurchased property:
public class Request
{
private Car car;
private Date datePurchased;
// standard getters setters
}
To control the String format of a date and set it to, e.g., yyyy-MM-dd HH:mm a z, consider the following snippet:
ObjectMapper objectMapper = new ObjectMapper();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
objectMapper.setDateFormat(df);
String carAsString = objectMapper.writeValueAsString(request);
// output: {"car":{"color":"yellow","type":"renault"},"datePurchased":"2016-07-03 11:43 AM CEST"}
To learn more about serializing dates with Jackson, read our more in-depth write-up.
Please note that in some cases, we need to specify Locale as well while creating SimpleDateFormat so that it gives consistent output across all machines irrespective of the region in which that machine is running.
To specify Locale we can do:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm a z", Locale.ENGLISH);
4.4. Handling Collections
Another small but useful feature available through the DeserializationFeature class is the ability to generate the type of collection we want from a JSON Array response.
For example, we can generate the result as an array:
String jsonCarArray =
"[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class);
// print cars
Or as a List:
String jsonCarArray =
"[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});
// print cars
More information about handling collections with Jackson is available here.
5. Builder Pattern for ObjectMapper
So far, weβve learned different ways to configure an ObjectMapper instance. In this section, weβll prototype the ObjectMapperBuilder class to create an immutable instance of ObjectMapper class.
5.1. ObjectMapperBuilder Class
Letβs start by creating the ObjectMapperBuilder class with a few configuration parameters, namely, enableIdentation, preserveOrder, and dateFormat:
public class ObjectMapperBuilder {
private boolean enableIndentation;
private boolean preserveOrder;
private DateFormat dateFormat;
}
We must note that there are several possible configurations for an ObjectMapper instance. Weβre focusing only on a subset of possible configurations for our use case of prototyping the builder.
Next, letβs add methods thatβll allow us to set the corresponding configuration properties to the builder while creating an instance of the ObjectMapper class:
ObjectMapperBuilder enableIndentation() {
this.enableIndentation = true;
return this;
}
ObjectMapperBuilder dateFormat() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("Asia/Kolkata")));
this.dateFormat = simpleDateFormat;
return this;
}
ObjectMapperBuilder preserveOrder(boolean order) {
this.preserveOrder = order;
return this;
}
Finally, letβs add the build() method to return the final ObjectMapper instance with the configured parameters:
public ObjectMapper build() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.enableIndentation);
objectMapper.setDateFormat(this.dateFormat);
if (this.preserveOrder) {
objectMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
}
return objectMapper;
}
Great! Weβve successfully prototyped the builder class for the ObjectMapper instance.
5.2. Builder in Action
Letβs start by creating a single instance of the ObjectMapper class with the help of the ObjectMapperBuilder class:
ObjectMapper mapper = new ObjectMapperBuilder()
.enableIndentation()
.dateFormat()
.preserveOrder(true)
.build();
Now, letβs define an instance of the Car class and its serialized JSON string:
Car givenCar = new Car("White", "Sedan");
String givenCarJsonStr = "{ \"color\" : \"White\", \"type\" : \"Sedan\" }";
Moving on, letβs deserialize givenCarJsonStr using the mapper object:
Car actual = mapper.readValue(givenCarJsonStr, Car.class);
Assertions.assertEquals("White", actual.getColor());
Assertions.assertEquals("Sedan", actual.getType());
Perfect! It looks like weβve got this one right.
Finally, letβs verify the serialization flow for an instance of the Request class:
Request request = new Request();
request.setCar(givenCar);
Date date = new Date(1684909857000L);
request.setDatePurchased(date);
String actual = mapper.writeValueAsString(request);
String expected = "{\n" + " \"car\" : {\n" + " \"color\" : \"White\",\n" +
" \"type\" : \"Sedan\"\n" + " },\n" + " \"datePurchased\" : \"2023-05-24 12:00 PM IST\"\n" +
"}";
Assertions.assertEquals(expected, actual);
Great! Weβve successfully verified the deserialization and serialization operations via the ObjectMapperBuilder class.
6. Conclusion
Jackson is a solid and mature JSON serialization/deserialization library for Java. The ObjectMapper API provides a straightforward way to parse and generate JSON response objects with a lot of flexibility. Additionally, we prototyped the ObjectMapperBuilder class to create immutable instances of the ObjectMapper class. This article discussed the main features that make the library so popular.
