1. 概要

当使用 JSON 格式作为 HTTP 请求协议时,Spring Boot将使用 ObjectMapper 对象序列化 HTTP Response 并反序列化 HTTP Request 。 在本教程中,我们将介绍最常用序列化配置方法。

要学习更多关于 Jackson 知识,请跳转到我们的 Jackson 教程.

2. 默认配置

Spring Boot 默认配置为:

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

让我们以一个例子开始:

  • 客户端将向我们发送GET请求 /coffee?name=Lavazza
  • Controller 返回 Coffee 对象
  • Spring 使用 ObjectMapperCoffee 对象 序列化为 JSON 字符串

下面我们将使用String和LocalDateTime对象,举例说明如何自定义序列化配置:

public class Coffee {

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

   //getters and setters
}

为了演示,我们还需要定义一个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);
}

默认配置下,请求 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中,我们可以自定义Spring默认的 ObjectMapper 配置,或定义一个新的ObjectMapper直接覆盖掉它。我们将在下面中介绍这2种方法。

3. 自定义默认的 ObjectMapper


在本节中,我们将看到如何自定义Spring Boot默认的 ObjectMapper

3.1. Application Properties & 自定义 Jackson 模块

最简单的方法是通过修改application.properties来自定义mapper配置。 配置的一般结构是 :

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

例如,如果我们要禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, 则添加:

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

除了上面,我们还可以配置其他:

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

该函数式接口的目的是允许我们创建configuration 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 时默认情况下使用此构建器,并且会自动选择已定义好的一个。

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

它会默认配置两项:

  • Disable MapperFeature.DEFAULT_VIEW_INCLUSION
  • Disable DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

根据Jackson2ObjectMapperBuilder 文档中说明, 如果一些模块存在搜索路径中,它们也会自动注册 :

  • jackson-datatype-jdk8: support for other Java 8 types like Optional
  • jackson-datatype-jsr310: support for Java 8 Date and Time API types
  • jackson-datatype-joda: support for Joda-Time types
  • jackson-module-kotlin: support for Kotlin classes and data classes

这种方法的优点是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());
}

请查看我们的Spring Http Message Converters文章以了解更多信息。

5. 配置测试

为了测试我们的配置是否生效,我们将使用 TestRestTemplate 并将结果序列化为 String 。 这样,我们可以验证 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自定义JSON序列化的几种方法。

和本站其他教程一样,教程源代码存放在GitHub.