Spring Boot 自定义 Jackson ObjectMapper

1、概览

Spring Boot 默认使用 Jackson ObjectMapper 实例来序列化和反序列化 JSON 格式的响应与请求。

本文将带你了解如何在 Spring Boot 中自定义 Jackson ObjectMapper 选项,以及配置序列化和反序列化选项的最常用方法。

2、默认配置

默认情况下,Spring Boot 禁用了以下功能:

  • MapperFeature.DEFAULT_VIEW_INCLUSION
  • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
  • SerializationFeature.WRITE_DATES_AS_TIMESTAMPS

从一个简单的例子开始:

  • 客户端向 /coffee?name=Lavazza 端点发送 GET 请求。
  • Controller 返回一个新的 Coffee 对象。
  • Spring 使用 ObjectMapper 将 POJO 序列化为 JSON。

使用 StringLocalDateTime 对象来演示自定义选项:

public class Coffee {

    private String name;
    private String brand;
    private LocalDateTime date;

   // get / set 省略
}

定义一个简单的 REST Controller 来演示序列化:

@GetMapping("/coffee")
public Coffee getCoffee(
        @RequestParam(required = false) String brand,
        @RequestParam(required = false) String name) {
    return new Coffee()
      .setBrand(brand)
      .setDate(FIXED_DATE)
      .setName(name);
}

默认情况下,调用 GET http://lolcahost:8080/coffee?brand=Lavazza 的响应如下:

{
  "name": null,
  "brand": "Lavazza",
  "date": "2020-11-16T10:21:35.974"
}

假如,我们希望排除 null 值,并使用自定义日期格式(dd-MM-yyyy HH:mm)。

最终的响应格式应该如下:

{
  "brand": "Lavazza",
  "date": "04-11-2020 10:34"
}

使用 Spring Boot 时,可以选择自定义或覆盖默认 ObjectMapper 来实现。

3、自定义默认 ObjectMapper

本节中将带你了解如何自定义 Spring Boot 使用的默认 ObjectMapper

3.1、Application Properties 和自定义 Jackson Module

配置 mapper 的最简单方法是通过 application.properties

配置的大致结构如下:

spring.jackson.<category_name>.<feature_name>=true,false

举例来说,禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS

spring.jackson.serialization.write-dates-as-timestamps=false

除了上述功能类别,还可以配置 “属性包含”(property inclusion):

spring.jackson.default-property-inclusion=always, non_null, non_absent, non_default, non_empty

完整的配置属性,可以参考 中文文档

配置属性是最简单的方法。这种方法的缺点是无法自定义高级选项,例如为 LocalDateTime 自定义日期格式。

此时,根据上述配置会得到如下结果:

{
  "brand": "Lavazza",
  "date": "2020-11-16T10:35:34.593"
}

可以使用自定义日期格式注册一个新的 JavaTimeModule 来实现 “自定义日期格式” 目标:

@Configuration
@PropertySource("classpath:coffee.properties")
public class CoffeeRegisterModuleConfig {

    @Bean
    public Module javaTimeModule() {
        JavaTimeModule module = new JavaTimeModule();
        module.addSerializer(LOCAL_DATETIME_SERIALIZER);
        return module;
    }
}

配置属性文件 coffee.properties 如下:

spring.jackson.default-property-inclusion=non_null

Spring Boot 会自动注册任何 com.fasterxml.jackson.databind.Module 类型的 Bean。下面是配置的最终结果:

{
  "brand": "Lavazza",
  "date": "16-11-2020 10:43"
}

3.2、Jackson2ObjectMapperBuilderCustomizer

该函数式接口用于创建配置 Bean。

这些配置 Bean 应用于通过 Jackson2ObjectMapperBuilder 创建的默认 ObjectMapper

@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
    return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL)
      .serializers(LOCAL_DATETIME_SERIALIZER);
}

配置 Bean 以特定顺序应用,可以使用 @Order 注解控制该顺序。如果想从不同的配置或模块中配置 ObjectMapper,那么这种优雅的方法非常适合。

4、覆盖默认配置

如果想完全控制配置,有几个方式可以禁用自动配置,只允许应用自定义的配置。

4.1、ObjectMapper

覆盖默认配置的最简单方法是定义一个 ObjectMapper Bean 并将其标记为 @Primary

@Bean
@Primary
public ObjectMapper objectMapper() {
    JavaTimeModule module = new JavaTimeModule();
    module.addSerializer(LOCAL_DATETIME_SERIALIZER);
    return new ObjectMapper()
      .setSerializationInclusion(JsonInclude.Include.NON_NULL)
      .registerModule(module);
}

当我们想完全控制序列化过程,又不想允许外部配置时,可以使用这种方法。

4.2、Jackson2ObjectMapperBuilder

另一种简单的方法是定义一个 Jackson2ObjectMapperBuilder Bean。

实际上,Spring Boot 在构建 ObjectMapper 时默认使用该 Builder,并会自动选择已定义的 Builder:

@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
    return new Jackson2ObjectMapperBuilder().serializers(LOCAL_DATETIME_SERIALIZER)
      .serializationInclusion(JsonInclude.Include.NON_NULL);
}

默认情况下,它配置两个选项:

  • 禁用 MapperFeature.DEFAULT_VIEW_INCLUSION
  • 禁用 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

根据 Jackson2ObjectMapperBuilder 文档,如果 classpath 上存在某些模块(module),它也会注册这些模块:

  • jackson-datatype-jdk8:支持其他 Java 8 类型,如 Optional
  • jackson-datatype-jsr310:支持 Java 8 Date 和 Time API 类型
  • jackson-datatype-joda:支持 Joda-Time 类型
  • jackson-module-kotlin:支持 Kotlin 类和数据类

这种方法的优势在于,Jackson2ObjectMapperBuilder 提供了一种简单直观的方法来构 ObjectMapper

4.3、MappingJackson2HttpMessageConverter

定义一个类型为 MappingJackson2HttpMessageConverter 的 Bean,Spring Boot 就会自动使用它:

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().serializers(LOCAL_DATETIME_SERIALIZER)
      .serializationInclusion(JsonInclude.Include.NON_NULL);
    return new MappingJackson2HttpMessageConverter(builder.build());
}

关于 HttpMessageConverter 的更多内容,可以参阅 中文文档

5、测试配置

使用 TestRestTemplate 并将对象序列化为字符串来测试配置。

验证 Coffee 对象序列化时没有 null 值,并且采用了自定义日期格式:

@Test
public void whenGetCoffee_thenSerializedWithDateAndNonNull() {
    String formattedDate = DateTimeFormatter.ofPattern(CoffeeConstants.dateTimeFormat).format(FIXED_DATE);
    String brand = "Lavazza";
    String url = "/coffee?brand=" + brand;
    
    String response = restTemplate.getForObject(url, String.class);
    
    assertThat(response).isEqualTo("{\"brand\":\"" + brand + "\",\"date\":\"" + formattedDate + "\"}");
}

6、总结

本文介绍了如何在 Spring Boot 中配置 Jackson ObjectMapper 选项以实现自定义 JSON 序列化/反序列化格式的几种方法。


Ref:https://www.baeldung.com/spring-boot-customize-jackson-objectmapper