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()
 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();
    }
}

3. Define the META-INF.services file (optional)

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/