JSON is not new to developers, as most of today’s web services, mobile applications, and even the Internet of Things use JSON as a data exchange format. Learning the tools for manipulating the JSON format is essential for developers. This article will describe how to use Jackson, an open source tool library, to perform common operations on JSON.
JSON Introduction
What is JSON?JSON stands for “JavaScript Object Notation”, JSON is a text-based format that can be understood as a structured data that can contain key-value mapping, nested objects and arrays of information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
{
"array": [
1,
2,
3
],
"boolean": true,
"color": "gold",
"null": null,
"number": 123,
"object": {
"a": "b",
"c": "d"
},
"string": "www.wdbyte.com"
}
|
Jackson Introduction
Jackson and FastJson as well, is a Java language written for JSON processing open source tool library , Jackson is very widely used , Spring Framework default use Jackson for JSON processing .
Jackson has three core packages, namely Streaming, Databid, Annotations, through which you can easily operate on JSON.
- Streaming in the
jackson-core
module. Defines a number of APIs related to stream processing and specific JSON implementations.
- Annotations in the
jackson-annotations
module, contains annotations in Jackson.
- Databind in the
jackson-databind
module, implements databinding on top of the Streaming
package, relying on Streaming
and Annotations
packages.
Thanks to Jackson’s highly extensible design, there are many common text formats and tools that have adaptations for Jackson, such as CSV, XML, YAML, etc.
Jackson Maven dependencies
When using Jackson, in most cases we just need to add the jackson-databind
dependency to use the Jackson functionality, which depends on the following two packages.
- com.fasterxml.jackson.core:jackson-annotations
- com.fasterxml.jackson.core:jackson-core
1
2
3
4
5
|
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
|
To facilitate the code demonstration that follows in this article, we import both Junit for unit testing and Lombok to reduce Get/Set code writing.
1
2
3
4
5
6
7
8
9
10
11
|
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
|
ObjectMapper
ObjectMapper
is one of the most commonly used classes in the Jackson library for fast conversion between Java objects and JSON strings. If you have used FastJson, then ObjectMapper
in Jackson is like the JSON class in FastJson.
There are some common methods in this class.
readValue()
method can perform JSON deserialization operations, such as converting strings, file streams, byte streams, byte arrays, and other common content into Java objects.
writeValue()
method can perform JSON serialization operations, which can convert Java objects into JSON strings.
Most of the time, ObjectMapper
works by mapping the Java bean objects through their Get/Set methods, so it is important to write the Get/Set methods of Java objects correctly, but ObjectMapper
also provides a lot of configurations. For example, you can customize the conversion process between Java objects and JSON strings through configuration or annotations. These are described in the following sections.
Jackson JSON Basic Operations
Jackson is a JSON tool library in Java, and handling JSON strings and Java objects is its most basic and common function. Here are some examples to demonstrate the usage.
Jackson JSON Serialization
Write a Person class that defines three properties, name, age, and skill.
1
2
3
4
5
6
7
8
9
|
/**
* @author https://www.wdbyte.com
*/
@Data
public class Person {
private String name;
private Integer age;
private List<String> skillList;
}
|
Converts a Java object to a JSON string.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
class PersonTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void pojoToJsonString() throws JsonProcessingException {
Person person = new Person();
person.setName("aLng");
person.setAge(27);
person.setSkillList(Arrays.asList("java", "c++"));
String json = objectMapper.writeValueAsString(person);
System.out.println(json);
String expectedJson = "{\"name\":\"aLng\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}";
Assertions.assertEquals(json, expectedJson);
}
}
|
The output JSON string is as follows.
1
|
{"name":"aLng","age":27,"skillList":["java","c++"]}
|
Jackson can even write the serialized JSON string directly to a file or read it as a byte array.
1
2
3
4
5
|
mapper.writeValue(new File("result.json"), myResultObject);
// or
byte[] jsonBytes = mapper.writeValueAsBytes(myResultObject);
// or
String jsonString = mapper.writeValueAsString(myResultObject);
|
Jackson JSON deserialization
The code is as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.wdbyte.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
class PersonTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void jsonStringToPojo() throws JsonProcessingException {
String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}";
Person person = objectMapper.readValue(expectedJson, Person.class);
System.out.println(person);
Assertions.assertEquals(person.getName(), "aLang");
Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");
}
}
|
The output is as follows.
1
|
Person(name=aLang, age=27, skillList=[java, c++])
|
The above example shows how to use Jackson to deserialize a JSON string into a Java object, but in fact it is just as easy for Jackson to deserialize a JSON string in a file, or in byte form.
First, prepare a JSON file Person.json with the following contents.
1
2
3
4
5
6
7
8
|
{
"name": "aLang",
"age": 27,
"skillList": [
"java",
"c++"
]
}
|
Read this file and deserialize it to a java object.
1
2
3
4
5
6
7
8
9
10
11
12
|
ObjectMapper objectMapper = new ObjectMapper();
@Test
void testJsonFilePojo() throws IOException {
File file = new File("src/Person.json");
Person person = objectMapper.readValue(file, Person.class);
// or
// person = mapper.readValue(new URL("http://some.com/api/entry.json"), MyValue.class);
System.out.println(person);
Assertions.assertEquals(person.getName(), "aLang");
Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");
}
|
The output is as follows.
1
|
Person(name=aLang, age=27, skillList=[java, c++])
|
JSON to List
The above demonstrates that JSON strings are all single objects, if JSON is a list of objects then how do you handle it using Jackson?
There is already a file PersonList.json
with the following contents.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
[
{
"name": "aLang",
"age": 27,
"skillList": [
"java",
"c++"
]
},
{
"name": "darcy",
"age": 26,
"skillList": [
"go",
"rust"
]
}
]
|
Read it and convert it to List<Person>
.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
ObjectMapper objectMapper = new ObjectMapper();
@Test
void fileToPojoList() throws IOException {
File file = new File("src/EmployeeList.json");
List<Person> personList = objectMapper.readValue(file, new TypeReference<List<Person>>() {});
for (Person person : personList) {
System.out.println(person);
}
Assertions.assertEquals(personList.size(), 2);
Assertions.assertEquals(personList.get(0).getName(), "aLang");
Assertions.assertEquals(personList.get(1).getName(), "darcy");
}
|
The output is as follows.
1
2
|
Person(name=aLang, age=27, skillList=[java, c++])
Person(name=darcy, age=26, skillList=[go, rust])
|
JSON to Map
JSON to Map is useful when we don’t have a Java object, here’s how to use Jackson to convert JSON text to Map objects.
1
2
3
4
5
6
7
8
9
10
11
12
|
ObjectMapper objectMapper = new ObjectMapper();
@Test
void jsonStringToMap() throws IOException {
String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}";
Map<String, Object> employeeMap = objectMapper.readValue(expectedJson, new TypeReference<Map>() {});
System.out.println(employeeMap.getClass());
for (Entry<String, Object> entry : employeeMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
Assertions.assertEquals(employeeMap.get("name"), "aLang");
}
|
The output is as follows.
1
2
3
4
|
class java.util.LinkedHashMap
name:aLang
age:27
skillList:[java, c++]
|
Jackson ignores fields
If when doing a JSON to Java object conversion. If there are properties in JSON that do not exist in the Java class, then an exception is thrown: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
.
Non-existent properties can be ignored by setting objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.
1
2
3
4
5
6
7
8
9
10
11
12
|
ObjectMapper objectMapper = new ObjectMapper();
@Test
void jsonStringToPojoIgnoreProperties() throws IOException {
// UnrecognizedPropertyException
String json = "{\"yyy\":\"xxx\",\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}";
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Person person = objectMapper.readValue(json, Person.class);
System.out.printf(person.toString());
Assertions.assertEquals(person.getName(), "aLang");
Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");
}
|
The output is as follows.
1
|
Person(name=aLang, age=27, skillList=[java, c++])
|
Before Java 8 we usually used the java.util.Date
class to handle time, but when Java 8 was released a new time class was introduced java.time.LocalDateTime
. The two are handled slightly differently in Jackson.
First create an Order class with two time type properties.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.wdbyte.jackson;
import java.time.LocalDateTime;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author https://www.wdbyte.com
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private Date createTime;
private LocalDateTime updateTime;
}
|
Date type
Let’s create a new test case to test the JSON conversion of the two time types.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.wdbyte.jackson;
import java.time.LocalDateTime;
import java.util.Date;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
class OrderTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void testPojoToJson0() throws JsonProcessingException {
Order order = new Order(1, new Date(), null);
String json = objectMapper.writeValueAsString(order);
System.out.println(json);
order = objectMapper.readValue(json, Order.class);
System.out.println(order.toString());
Assertions.assertEquals(order.getId(), 1);
}
}
|
In this test code, we have only initialized the Date
type property, and here is the output.
1
2
|
{"id":1,"createTime":1658320852395,"updateTime":null}
Order(id=1, createTime=Wed Jul 20 20:40:52 CST 2022, updateTime=null)
|
You can see that the JSON serialization and deserialization is done normally, but the time in the JSON is in a timestamp format, which may not be what we want.
LocalDateTime type
Why isn’t the value of the LocalDateTime
type property set? Because by default JSON conversion of the LocalDateTime class throws an exception.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* @author https://www.wdbyte.com
*/
class OrderTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void testPojoToJson() throws JsonProcessingException {
Order order = new Order(1, new Date(), LocalDateTime.now());
String json = objectMapper.writeValueAsString(order);
System.out.println(json);
order = objectMapper.readValue(json, Order.class);
System.out.println(order.toString());
Assertions.assertEquals(order.getId(), 1);
}
}
|
Running it will throw an exception.
1
2
3
4
|
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Java 8 date/time type `java.time.LocalDateTime` not supported by default:
add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
to enable handling (through reference chain: com.wdbyte.jackson.Order["updateTime"])
|
Here we need to add the appropriate data binding support libraries.
Adding dependencies.
1
2
3
4
5
|
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.3</version>
</dependency>
|
Then the dependencies are registered with the findAndRegisterModules()
method when the ObjectMapper is defined.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import java.time.LocalDateTime;
import java.util.Date;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
class OrderTest {
ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
@Test
void testPojoToJson() throws JsonProcessingException {
Order order = new Order(1, new Date(), LocalDateTime.now());
String json = objectMapper.writeValueAsString(order);
System.out.println(json);
order = objectMapper.readValue(json, Order.class);
System.out.println(order.toString());
Assertions.assertEquals(order.getId(), 1);
}
}
|
Running it gives you the normal serialization and deserialization logs, though the time format after serialization remains strange.
1
2
|
{"id":1,"createTime":1658321191562,"updateTime":[2022,7,20,20,46,31,567000000]}
Order(id=1, createTime=Wed Jul 20 20:46:31 CST 2022, updateTime=2022-07-20T20:46:31.567)
|
Customize the time format by using the annotation @JsonFormat
on the field.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import java.time.LocalDateTime;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime updateTime;
}
|
Run the above column again to get the time-formatted JSON string.
1
2
|
{"id":1,"createTime":"2022-07-20 20:49:46","updateTime":"2022-07-20 20:49:46"}
Order(id=1, createTime=Wed Jul 20 20:49:46 CST 2022, updateTime=2022-07-20T20:49:46)
|
Jackson common annotations
@JsonIgnore
Use @JsonIgnore
to ignore attributes in a Java object that will not participate in JSON serialization and deserialization.
Example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.wdbyte.jackson;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
/**
* @author https://www.wdbyte.com
*/
@Data
public class Cat {
private String name;
@JsonIgnore
private Integer age;
}
|
Write unit test classes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.wdbyte.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
class CatTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void testPojoToJson() throws JsonProcessingException {
Cat cat = new Cat();
cat.setName("Tom");
cat.setAge(2);
String json = objectMapper.writeValueAsString(cat);
System.out.println(json);
Assertions.assertEquals(json, "{\"name\":\"Tom\"}");
cat = objectMapper.readValue(json, Cat.class);
Assertions.assertEquals(cat.getName(), "Tom");
Assertions.assertEquals(cat.getAge(), null);
}
}
|
You can see that the value of the age
property in the output is null
.
@JsonGetter
Use @JsonGetter
to customize property names when JSON serializing Java objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
/**
* @author https://www.wdbyte.com
*/
@Data
public class Cat {
private String name;
private Integer age;
@JsonGetter(value = "catName")
public String getName() {
return name;
}
}
|
Write unit test classes for testing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
class CatTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void testPojoToJson2() throws JsonProcessingException {
Cat cat = new Cat();
cat.setName("Tom");
cat.setAge(2);
String json = objectMapper.writeValueAsString(cat);
System.out.println(json);
Assertions.assertEquals(json, "{\"age\":2,\"catName\":\"Tom\"}");
}
}
|
The output is as follows. java object’s name
property name has been serialized to catName
.
1
|
{"age":2,"catName":"Tom"}
|
@JsonSetter
Use @JsonSetter
to set the mapping of keys in JSON to Java properties when deserializing JSON.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.wdbyte.jackson;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter;
import lombok.Data;
/**
* @author https://www.wdbyte.com
* @date 2022/07/17
*/
@Data
public class Cat {
@JsonSetter(value = "catName")
private String name;
private Integer age;
@JsonGetter(value = "catName")
public String getName() {
return name;
}
}
|
Write unit test classes for testing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
class CatTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void testPojoToJson2() throws JsonProcessingException {
String json = "{\"age\":2,\"catName\":\"Tom\"}";
Cat cat = objectMapper.readValue(json, Cat.class);
System.out.println(cat.toString());
Assertions.assertEquals(cat.getName(), "Tom");
}
}
|
The output is as follows.
@JsonAnySetter
The @JsonAnySetter
allows you to handle all the properties that do not exist in the Java object when deserializing JSON. The following code demonstrates storing non-existent properties into a Map collection.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author https://www.wdbyte.com
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String name;
private Integer age;
private Map<String, Object> diyMap = new HashMap<>();
@JsonAnySetter
public void otherField(String key, String value) {
this.diyMap.put(key, value);
}
}
|
Write unit test cases.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
class StudentTest {
private ObjectMapper objectMapper = new ObjectMapper();
@Test
void testJsonToPojo() throws JsonProcessingException {
Map<String, Object> map = new HashMap<>();
map.put("name", "aLang");
map.put("age", 18);
map.put("skill", "java");
String json = objectMapper.writeValueAsString(map);
System.out.println(json);
Student student = objectMapper.readValue(json, Student.class);
System.out.println(student);
Assertions.assertEquals(student.getDiyMap().get("skill"), "java");
}
}
|
You can see from the output that the skill
property in JSON is placed in the diyMap
collection because it is not in the Java class Student
.
1
2
|
{"skill":"java","name":"aLang","age":18}
Student(name=aLang, age=18, diyMap={skill=java})
|
@JsonAnyGetter
Use @JsonAnyGetter
to make the Map collection of Java objects as the source of properties in JSON when serializing them. Here’s an example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* @author https://www.wdbyte.com
*/
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student {
@Getter
@Setter
private String name;
@Getter
@Setter
private Integer age;
@JsonAnyGetter
private Map<String, Object> initMap = new HashMap() {{
put("a", 111);
put("b", 222);
put("c", 333);
}};
}
|
The output is as follows.
1
|
{"name":"aLang","age":20,"a":111,"b":222,"c":333}
|
Jackson Summary
- Jackson is one of the more popular JSON processing libraries in Java, and it is the default JSON tool for Spring.
- Jackson has three main modules, Streaming API, Annotations and Data Binding.
- The ObjectMapper class in Jackson is very powerful for JSON related processing and can be combined with annotations and configuration for custom conversion logic.
- Jackson is very extensible, such as CSV, XML, YAML format processing have corresponding adaptations for Jackson and so on.
The code related to this article can be found here.
Reference https://www.wdbyte.com/tool/jackson.html