JSON is the accepted standard these days for representing, storing, and transferring data so every developer should know how to work with it. Since the Java Standard library doesn't provide a way to convert Java objects to JSON and vice versa, we have to use third-party libraries like Jackson. Jackson is a powerful, lightweight, and flexible Java library that is used by default by many other popular frameworks such as Spring, ElasticSearch, OkHttp, Hadoop, and so on. Because it's so widely used, Jackson is a must-have tool for all Java software developers.
Dependency
To work on our project with the Jackson library, you must add the jackson-databind dependency which contains two other necessary dependencies, namely jackson-annotations and jackson-core. Its latest version is currently 2.15.3.
You can always find the latest version on the Maven website.
Gradle
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.3'Maven
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version>
</dependency>Default serialization
Let's imagine that we are developing a Twitter-like app and one of the main entities is a post. We want to describe the main components of the post: id, creation date, content, number of likes, and a list of comments (this is a rather simplistic model, but it's enough for us now).
public class Post {
private int id;
private Date createdDate;
private String content;
private int likes;
private List<String> comments;
// contructor, getters, setters
}
Jackson provides us with ObjectMapper which you will use for serializing/deserializing. You can see how straightforward it is in the following example:
// Step 1
Post post = new Post(
1,
new Date(),
"I learned how to use jackson!",
10,
Arrays.asList("Well done!", "Great job!")
);
// Step 2
ObjectMapper objectMapper = new ObjectMapper();
// Step 3
String postAsString = objectMapper.writeValueAsString(post);
System.out.println(postAsString);
In Step 1, you create a post with some data. In Step 2, you create an ObjectMapper which provides us with an easy way to convert Java objects to JSON and vice versa. In Step 3, you use the writeValueAsString method which generates JSON and returns it as a string.
After running the code in the console, you will get the following result:
{"id":1,"createdDate":1655112861424,"content":"I learned how to use jackson!","likes":10,"comments":["Well done!","Great job!"]}
It is important to note that we are using the Date from java.util because Jackson supports it by default, and Jackson will serialize the Date to the timestamp format (number of milliseconds since January 1st, 1970, UTC).
You have converted the object to JSON in a few lines of code, but reading it in such a form is quite inconvenient. Let's get a formatted JSON string: for this purpose, you will use the writerWithDefaultPrettyPrinter() method for constructing a writer that will serialize objects using prettyprinter for indentation.
String postAsString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(post);
Here is our JSON:
{
"id" : 1,
"createdDate" : 1654027200000,
"content" : "I learned how to use jackson!",
"likes" : 10,
"comments" : [ "Well done!", "Great job!" ]
}The @JsonProperty annotation
As you see above, Jackson uses field names (id, createdDate, etc) as a key in JSON. But what if you need to change the key or don't want to put any field in JSON at all? Do you need to completely change the entire class? Jackson developers took care of these situations and provided us with a set of annotations you can use for such cases. Let's look at the most basic of them in this and the next sections.
The first annotation you will talk about is @JsonProperty. You can use this annotation in different cases, two of which you'll discuss below.
Firstly, @JsonProperty allows you to change the name used in JSON as a key. Let's change the name from createdDate to postedAt:
public class Post {
private int id;
@JsonProperty("postedAt")
private Date createdDate;
private String content;
private int likes;
private List<String> comments;
}
Here is our JSON:
{
"id" : 1,
"content" : "I learned how to use jackson!",
"likes" : 10,
"comments" : [ "Well done!", "Great job!" ],
"postedAt" : 1654027200000 // here
}
As you can see, now you have postedAt instead of createdDate. Pretty easy, isn't it?
The second case is to use @JsonProperty to denote a non-standard getter/setter that will be used on a JSON property.
For example, if you want to display createdDate in a readable format (like 01-01-2000), you need to put @JsonProperty above the method that will do this conversion:
public class Post {
private int id;
private Date createdDate;
private String content;
private int likes;
private List<String> comments;
@JsonProperty("createdDate")
public String getReadableCreatedDate() {
return (new SimpleDateFormat("dd-MM-yyyy")).format(createdDate);
}
// contructor, getters, setters
}
Here is our JSON with the formatted date:
{
"id" : 1,
"createdDate" : "01-06-2022",
"content" : "I learned how to use jackson!",
"likes" : 10,
"comments" : [ "Well done!", "Great job!" ]
}@JsonIgnore
@JsonIgnore helps us ignore some Java class fields.
For instance, the message ID (id) is sensitive information and you would not want anyone to know it, so it's best to ignore this field.
public class Post {
@JsonIgnore
private int id;
private Date createdDate;
private String content;
private int likes;
private List<String> comments;
}
Now let's confirm that there is no id in JSON:
{
"content" : "I learned how to use jackson!",
"likes" : 10,
"comments" : [ "Well done!", "Great job!" ],
"createdDate" : 1654027200000
}@JsonPropertyOrder
If you look closely at the example with the @JsonProperty annotation, you can see that after changing the name from createdDate to postedAt in JSON, this field is displayed last. Does it mean that the order of the fields is strictly regulated and you must follow it?
By default, the ordering of the fields in the serialized JSON depends on the JDK. They may come in the declaration order but it's not guaranteed.
@JsonPropertyOrder allows us to set a specific order when serializing a Java object.
@JsonPropertyOrder({
"likes",
"comments",
"createdDate", // here you can also use 'postedAt'
"content",
})
public class Post {
@JsonIgnore
private int id;
@JsonProperty("postedAt")
private Date createdDate;
private String content;
private int likes;
private List<String> comments;
}
Let's check that the order has changed:
{
"likes" : 10,
"comments" : [ "Well done!", "Great job!" ],
"postedAt" : 1654027200000,
"content" : "I learned how to use jackson!"
}
As you can see, you used createdDate in @JsonPropertyOrder but there wouldn't be a problem if you also used postedAt.
Deserialization
Knowing how to convert Java objects to JSON, you also need to understand how to do the reverse conversion. For this, you use the readValue method from ObjectMapper.
String inputJson = "{\"id\":1,\"createdDate\":1654027200000,\"content\":\"I learned how to use jackson!\",\"likes\":10,\"comments\":[\"Well done!\",\"Great job!\"]}\n";
ObjectMapper objectMapper = new ObjectMapper();
Post post = objectMapper.readValue(inputJson, Post.class);
Let's dive deeper into it and talk about the restrictions for the class that you will create from the JSON string. For Jackson to be able to convert JSON into a Java object of some class, this class must have a default constructor or a suitable creator method that Jackson can use for deserialization. The choice between these two approaches depends on the design of a class and your specific requirements. Here's a brief overview of both approaches:
Default Constructor Approach: The class must have an empty constructor, and the fields must not be
final. In this case, Jackson will first create an instance of the class, and then put values into all fields through reflection. Setters are only needed if you want to change the value in the fields — Jackson doesn't use them.
public class Post {
private int id;
private Date createdDate;
private String content;
private int likes;
private List<String> comments;
public Post() {
}
// getters, setters
}
Custom Creator Approach: The class must have a constructor with the
@JsonCreatorannotation, and all its parameters must have the@JsonPropertyannotation, which must necessarily contain the name from JSON. This approach is useful when you have complex initialization logic or need to handle deserialization in a specific way.
public class Post {
private final int id;
private final Date createdDate;
private String content;
private int likes;
private List<String> comments;
@JsonCreator
public Post(
@JsonProperty("id") int id,
@JsonProperty("createdDate") Date createdDate,
@JsonProperty("content") String content,
@JsonProperty("likes") int likes,
@JsonProperty("comments") List<String> comments) {
this.id = id;
this.createdDate = createdDate;
this.content = content;
this.likes = likes;
this.comments = comments;
}
// getters
}Additional cases
The basics of working with Jackson have been described above. The library itself is quite large and provides great features of working with JSON. In this segment we'll take a look at several more cases that you might find useful for your projects.
Java records can also be easily serialized and deserialized using Jackson's ObjectMapper. Furthermore, if there is no requirement for customized serialization or deserialization, no annotations are necessary.
record Person(String name, int age) {}
// Serialization of Java records
ObjectMapper objectMapper = new ObjectMapper();
Person person = new Person("Alice", 20);
String personJson = objectMapper.writeValueAsString(person);
// Deserialization of Java records
String json = "{\"name\":\"Bob\",\"age\":30}";
Person deserializedPerson = objectMapper.readValue(json, Person.class);
Let's get back to our Twitter-like app and its Post entity. Do such applications contain only one post? Of course not. Jackson allows for the serialization and deserialization of multiple instances:
// Serialization of Lists of objects
List<Post> posts = List.of(
new Post(1, new Date(), "Content1", 10, Arrays.asList("Comment1", "Comment2")),
new Post(2, new Date(), "Content2", 42, Arrays.asList("Comment3", "Comment4"))
);
String postsJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(posts);
System.out.println(postsJson);
After running this code, you will get the JSON string, containing two instances:
[ {
"likes" : 10,
"comments" : [ "Comment1", "Comment2" ],
"createdDate" : 1697894696259,
"content" : "Content1",
"id" : 1
}, {
"likes" : 42,
"comments" : [ "Comment3", "Comment4" ],
"createdDate" : 1697894696259,
"content" : "Content2",
"id" : 2
} ]
The conversion back to Java objects can be performed as follows:
// Deserialization of Lists of objects
List<Post> deserializedPosts = objectMapper.readValue(postsJson, List.class);
Finally, consider the following problem: each post should have its own author, however, there is no such field in our class. Let's combine all the knowledge we've gained and use the Person record we've implemented before to address each post to a user.
record Person(String name, int age) {}
public class Post {
private int id;
private Date createdDate;
private String content;
private int likes;
private List<String> comments;
private Person person;
public Post() {
}
// getters, setters
}
Jackson allows you to serialize and deserialize composed objects the same way as earlier:
// Serialization of composed objects
Post post = new Post(1,
new Date(),
"Content1",
10, Arrays.asList("Comment1", "Comment2"),
new Person("Alice",20)
);
String postJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(post);
// Deserialization of composed objects
Post deserializedPosts = objectMapper.readValue(postJson, Post.class);
Here is the JSON we'll get in such case:
{
"id" : 1,
"createdDate" : 1697895998654,
"content" : "Content1",
"likes" : 10,
"comments" : [ "Comment1", "Comment2" ],
"person" : {
"name" : "Alice",
"age" : 20
}
}Conclusion
In this topic, you got acquainted with Jackson and discussed how you can use it to convert Java objects to JSON, and back. You also learned about some of the basic annotations that you will constantly encounter while working with Jackson. Specifically, you have covered the following:
ObjectMapperis a Jackson serializer/deserializer and you must usewriteValueAsStringandreadValuefor these operations respectively;How to use a few basic annotations such as
@JsonProperty,@JsonIgnore, and@JsonPropertyOrder;How deserialization works and what constraints a class must conform to;
How to utilize Jackson for serialization and deserialization of Java records, lists of objects and composed objects.
As you can see, Jackson is a very flexible library with extensive support for annotations, some of which you have seen in this topic, but it's just scratching the surface of flexibility you can obtain by using them correctly. Now, let's move on and solve some tasks!