Jackson is a JSON (return) serialization tool in the Java ecosystem, which is efficient, powerful, and secure (without as many security vulnerabilities as Fastjson). It is also widely used, with Spring Boot/Cloud, Akka, Spark and many other frameworks using it as the default JSON processing tool.
Dependency
To use Jackson, you need to add the following dependencies to your project (note: you don’t need to add them manually when using Spring Boot, they are already included by default in the Spring framework)
1
2
3
4
5
6
7
8
9
10
|
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.11.1</version>
</dependency>
|
Sbt:
1
2
3
4
|
libraryDependencies ++= Seq(
"com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.11.1",
"com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % "2.11.1"
)
|
- jsr310: Java 8 new date, time type support (java.time.*) support
- jdk8: Java 8 newly added data type support (Optional, etc.)
Easy to use
Get Jackson
Jackson requires an ObjectMapper object to be instantiated before it can be used (it does not directly provide a global default static method). Usually we define the objectMapper as a static member or inject it for use through the DI framework.
Java
1
2
|
public static final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules()
|
Spring
1
2
|
@Autowired
private ObjectMapper objectMapper;
|
Note: Spring does not automatically load all Jackson Modules for the classpath path by default, you need to register them manually by calling the registerModule method on the objectMapper.
Scala
1
|
val objectMapper = new ObjectMapper().findAndRegisterModules()
|
Recommended Jackson Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
ObjectMapper objectMapper = new ObjectMapper()
// Automatically load all Jackson Modules in the classpath
.findAndRegisterModules()
// Time zone serialization in the form of +08:00
.configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, false)
// Date, time serialization to string
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
// Duration serialization as a string
.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
// Instead of reporting an error when there is an unknown property in the Java class, this JSON field is ignored
.configure(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS, false)
// Enumerated types call the `toString` method for serialization
.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true)
// Set the serialization format of the java.util.
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
// Set the time zone used by Jackson
.setTimeZone(SimpleTimeZone.getTimeZone("GMT+8"));
|
Creating a JSON Object
Jackson’s ArrayNode
and ObjectNode
objects can’t be created directly, they need to be created via objectMapper
. Also, both Node
objects are subclasses of JsonNode
.
1
2
3
4
5
|
ArrayNode jsonArray = objectMapper.createArrayNode();
jsonArray.add("Jackson").add("JSON");
ObjectNode jsonObject = objectMapper.createObjectNode()
.put("title", "Json 之 Jackson")
.put("readCount", 1024);
|
Deserialization
1
2
3
4
5
6
7
8
9
10
|
String jsonText = ....;
// json text -> Jackson json node
JsonNode javaTimeNode = objectMapper.readTree(jsonText);
// json text -> java class
JavaTime javaTime1 = objectMapper.readValue(jsonText, JavaTime.class);
// Jackson json node -> java class
JavaTime javaTime2 = objectMapper.treeToValue(javaTimeNode, JavaTime.class);
|
Serialization
1
2
3
4
5
6
7
8
9
10
11
12
13
|
ZonedDateTime zdt = ZonedDateTime.parse("2020-07-02T14:31:28.822+08:00[Asia/Shanghai]");
JavaTime javaTime = new JavaTime()
.setLocalDateTime(zdt.toLocalDateTime())
.setZonedDateTime(zdt)
.setOffsetDateTime(zdt.toOffsetDateTime())
.setLocalDate(zdt.toLocalDate())
.setLocalTime(zdt.toLocalTime())
.setDuration(Duration.parse("P1DT1H1M1.1S"))
.setDate(Date.from(zdt.toInstant()))
.setTimestamp(Timestamp.from(zdt.toInstant()));
out.println(objectMapper.writeValueAsString(javaTime));
out.println(objectMapper.writeValueAsString(jsonObject));
out.println(objectMapper.writeValueAsString(jsonArray));
|
Output:
1
2
3
|
{"localDateTime":"2020-07-02T14:31:28.822","zonedDateTime":"2020-07-02T14:31:28.822+08:00","offsetDateTime":"2020-07-02T14:31:28.822+08:00","localDate":"2020-07-02","localTime":"14:31:28.822","duration":"PT25H1M1.1S","date":"2020-07-02 14:31:28","timestamp":"2020-07-02 14:31:28"}
{"title":"Json 之 Jackson","readCount":1024}
["Jackson","JSON"]
|
Java class to Jackson JsonNode
1
|
JsonNode jsonNode = objectMapper.valueToTree(javaTime);
|
Pretty output
1
|
objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(javaTime)
|
Ignore certain fields during serialization
@JsonIgnore
1
2
|
@JsonIgnore
private Long _version;
|
The @JsonIgnore
annotation can also be added to the getter function.
@JsonIgnoreProperties
1
2
3
|
@JsonIgnoreProperties({"_version", "timestamp"})
public class JavaTime {
}
|
Custom serializers, deserializers
For some custom types or types not supported by Jackson, you can implement your own serializer and deserializer.
POJO
Use the @JsonSerialize
and @JsonDeserialize
annotations on the type to specify the serializer and deserializer, respectively.
1
2
3
4
5
6
7
8
|
@Data
public class User {
private String id;
@JsonSerialize(using = PgJsonSerializer.class)
@JsonDeserialize(using = PgJsonDeserializer.class)
private io.r2dbc.postgresql.codec.Json metadata;
}
|
First convert JSON
from R2DBC
PostgreSQL
to JsonNode
object and then call gen.writeTree
to serialize it. This ensures that the serialized field value is a JSON
object or a JSON
array.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import io.r2dbc.postgresql.codec.Json;
public class PgJsonSerializer extends StdSerializer<Json> {
public PgJsonSerializer() {
super(Json.class);
}
@Override
public void serialize(Json value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
JsonParser parser = gen.getCodec().getFactory().createParser(value.asArray());
JsonNode node = gen.getCodec().readTree(parser);
gen.writeTree(node);
}
}
|
Read JsonParse
as a TreeNode
object via ObjectCodec#readTree
, serialize it to JSON
format (an array of characters of the JSON
string) and pass it to the Json.of
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import io.r2dbc.postgresql.codec.Json;
public class PgJsonDeserializer extends StdDeserializer<Json> {
public PgJsonDeserializer() {
super(Json.class);
}
@Override
public Json deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
TreeNode node = p.getCodec().readTree(p);
ObjectMapper objectMapper = (ObjectMapper) p.getCodec();
byte[] value = objectMapper.writeValueAsBytes(node);
return Json.of(value);
}
}
|
Jackson Module
When custom serialization/deserialization becomes more frequent, it becomes tedious to manually specify serializers/deserializers via annotations on each class. We can do this by defining a Jackson Module
and using .findAndRegisterModules()
to automatically register to the ObjectMapper
via Java
’s Service
mechanism. After registering serializers and deserializers of types through the Module
mechanism, there is no need to define the @JsonSerialize
and @JsonDeserialize
annotations on properties or methods.
1. Define Serializers and Deserializers
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
|
public class ExampleSerializers extends Serializers.Base implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Override
public JsonSerializer<?> findSerializer(
SerializationConfig config, JavaType type, BeanDescription beanDesc) {
final Class<?> raw = type.getRawClass();
if (Json.class.isAssignableFrom(raw)) {
return new PgJsonSerializer();
}
return super.findSerializer(config, type, beanDesc);
}
}
public class ExampleDeserializers extends Deserializers.Base implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Override
public JsonDeserializer<?> findBeanDeserializer(
JavaType type, DeserializationConfig config, BeanDescription beanDesc)
throws JsonMappingException {
if (type.hasRawClass(Optional.class)) {
return new PgJsonDeserializer();
}
return super.findBeanDeserializer(type, config, beanDesc);
}
}
|
2. Implementing Module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class ExampleModule extends com.fasterxml.jackson.databind.Module {
@Override
public void setupModule(SetupContext context) {
context.addSerializers(new ExampleSerializers());
context.addDeserializers(new ExampleDeserializers());
}
@Override
public String getModuleName() {
return "ExampleModule";
}
@Override
public Version version() {
return Version.unknownVersion();
}
}
|
Through the Java ServiceLoader
mechanism, Jackson
can automatically register the configured Module
. In the com.fasterxml.jackson.databind.Module
configuration file, specify the full path to the Module
that needs to be automatically registered, multiple Modules
can be written in multiple lines. Note: services
profile must be com.fasterxml.jackson.databind.Module
.
1
2
3
4
|
src/main/resources/
├── META-INF
│ └── services
│ └── com.fasterxml.jackson.databind.Module
|
If the services
file is not configured, it cannot be loaded automatically when calling objectMapper.findAndRegisterModules()
and needs to be registered manually via the objectMapper.registerModule
method, as follows.
1
|
objectMapper.registerModule(new ExampleModule());
|
Spring
Spring Boot Configuration Files
1
2
3
4
5
6
7
8
9
10
11
|
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
locale: zh_CN
serialization:
WRITE_DATES_WITH_ZONE_ID: false
WRITE_DURATIONS_AS_TIMESTAMPS: false
WRITE_DATES_AS_TIMESTAMPS: false
FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS: false
WRITE_ENUMS_USING_TO_STRING: true
|
WebFlux load jackson-module-scala
Adding dependencies.
1
2
3
4
5
|
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-scala_2.12</artifactId>
<version>2.11.1</version>
</dependency>
|
Configure JsonDecoder
and JsonEncoder
in configureHttpMessageCodecs
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@EnableWebFlux
@Configuration
public class CoreWebConfiguration implements WebFluxConfigurer {
@Autowired
private ObjectMapper objectMapper;
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
ServerCodecConfigurer.ServerDefaultCodecs defaultCodecs = configurer.defaultCodecs();
defaultCodecs.enableLoggingRequestDetails(true);
objectMapper.registerModule(new DefaultScalaModule());
defaultCodecs.jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON, MediaType.APPLICATION_STREAM_JSON));
defaultCodecs.jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON, MediaType.APPLICATION_STREAM_JSON));
}
}
|
Summary
Troubled by the many JSON libraries in the Java world, don’t hesitate to use Jackson, Gson, Fastjson ……! In addition to supporting Scala data types, it also supports Kotlin, so what better choice for JVM multilingual development?
Reference https://yangbajing.me/2020/07/04/json-%E4%B9%8B-jackson/