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。
使用 String
和 LocalDateTime
对象来演示自定义选项:
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