Spring Boot 如何自定义 Jackson JsonMapper 调整 JSON 序列化行为?
【免费下载链接】spring-bootSpring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.项目地址: https://gitcode.com/gh_mirrors/sp/spring-boot
如果你的 Spring Boot 应用使用 Jackson 3 序列化 JSON(任何@RestController在 Jackson 3 位于 classpath 上时默认就渲染 JSON 响应),你可能需要调整自动配置的tools.jackson.databind.json.JsonMapper:开启缩进输出、控制空值属性是否包含、注册自定义序列化器,甚至彻底替换默认的 Mapper。Spring Boot 提供了从环境属性到 Bean 定制、再到完全替换的多个层次的控制点,本文按从轻到重的顺序说明每条路径,并给出文档中示例代码的写法。
前提:应用中存在自动配置的JsonMapper。只要 Jackson 3 在 classpath 上,Spring Boot 就会自动配置tools.jackson.databind.json.JsonMapperBean,它包含在spring-boot-starter-json中。注意:Jackson 2 的支持已弃用,仅在从 Jackson 2 迁移时保留,长期项目不应依赖它(Jackson 2 走spring.jackson2.*属性和Jackson2ObjectMapperBuilderCustomizer,与本文的 Jackson 3 路径不同)。
通过 spring.jackson.* 环境属性调整序列化行为
最轻量的方式是直接用环境属性配置JsonMapper。Jackson 提供了一系列 on/off feature,它们在 Jackson 中由枚举定义,在 Spring Boot 中映射为环境属性:
| 枚举 | 属性 | 取值 |
|---|---|---|
tools.jackson.databind.cfg.DateTimeFeature | spring.jackson.datatype.datetime.<feature_name> | true、false |
tools.jackson.databind.cfg.EnumFeature | spring.jackson.datatype.enum.<feature_name> | true、false |
tools.jackson.databind.cfg.JsonNodeFeature | spring.jackson.datatype.json-node.<feature_name> | true、false |
com.fasterxml.jackson.annotation.JsonInclude$Include | spring.jackson.default-property-inclusion | always、non_null、non_absent、non_default、non_empty |
tools.jackson.databind.DeserializationFeature | spring.jackson.deserialization.<feature_name> | true、false |
tools.jackson.core.json.JsonReadFeature | spring.jackson.json.read.<feature_name> | true、false |
tools.jackson.core.json.JsonWriteFeature | spring.jackson.json.write.<feature_name> | true、false |
tools.jackson.databind.MapperFeature | spring.jackson.mapper.<feature_name> | true、false |
tools.jackson.databind.SerializationFeature | spring.jackson.serialization.<feature_name> | true、false |
例如,要开启 pretty print,设置spring.jackson.serialization.indent_output=true。这里用到了宽松绑定(relaxed binding):属性中indent_output的大小写不必与对应枚举常量INDENT_OUTPUT完全一致。
这套环境配置应用到自动配置的JsonMapper.BuilderBean 上,因此对它和由该 builder 创建的任意 mapper(包括自动配置的JsonMapperBean)都生效。所有属性定义见 JacksonProperties.java,它通过@ConfigurationProperties("spring.jackson")绑定,还提供date-format、property-naming-strategy、time-zone、locale、default-lenience、constructor-detector等字段。
通过定制器 Bean 做编程式微调
属性覆盖不了一切的场景(比如要按条件设置),Spring Boot 提供了两类定制器 Bean:
org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer:定制上下文中的JsonMapper.Builder。定制器可以排序——Boot 自带的定制器 order 为 0,因此你可以把自己的定制逻辑放在 Boot 的定制之前或之后。org.springframework.boot.jackson.autoconfigure.JsonFactoryBuilderCustomizer:定制 builder 及其创建 mapper 所用的JsonFactory;也可以通过各种spring.jackson.factory属性配置工厂。
模块注册同样有两条自动通道:
- 任意
tools.jackson.databind.JacksonModule类型的 Bean 都会自动注册到自动配置的JsonMapper.Builder,并作用于它创建的所有JsonMapper实例。这是应用级贡献自定义模块的机制,为应用新增功能时推荐使用。 - 通过 Java
ServiceLoader机制参与发现的模块默认也会被找到并加入自动配置的JsonMapper.Builder。如果不需要这个行为,把spring.jackson.find-and-add-modules设为false。
用 @JacksonComponent 注册自定义序列化器和反序列化器
如果你要自己写tools.jackson.databind.ValueSerializer/tools.jackson.databind.ValueDeserializer,常规做法是通过模块注册到 Jackson;Spring Boot 提供了替代方案:@org.springframework.boot.jackson.JacksonComponent注解,可以直接注册 Spring Bean。
它可以直接标注在ValueSerializer、ValueDeserializer或KeyDeserializer实现上,也可以标注在把这些类作为内部类的类上。Spring Boot 文档中的示例如下(来自 MyJacksonComponent.java,使用时把MyObject换成你的对象类型):
import tools.jackson.core.JsonGenerator; import tools.jackson.core.JsonParser; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.JsonNode; import tools.jackson.databind.SerializationContext; import tools.jackson.databind.ValueDeserializer; import tools.jackson.databind.ValueSerializer; import org.springframework.boot.jackson.JacksonComponent; @JacksonComponent public class MyJacksonComponent { public static class Serializer extends ValueSerializer<MyObject> { @Override public void serialize(MyObject value, JsonGenerator jgen, SerializationContext context) { jgen.writeStartObject(); jgen.writeStringProperty("name", value.getName()); jgen.writeNumberProperty("age", value.getAge()); jgen.writeEndObject(); } } public static class Deserializer extends ValueDeserializer<MyObject> { @Override public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) { JsonNode tree = jsonParser.readValueAsTree(); String name = tree.get("name").stringValue(); int age = tree.get("age").intValue(); return new MyObject(name, age); } } }所有ApplicationContext中的@JacksonComponentBean 都会自动注册到 Jackson。由于@JacksonComponent元标注了@Component,常规的组件扫描规则适用。
Spring Boot 还提供了org.springframework.boot.jackson.ObjectValueSerializer和org.springframework.boot.jackson.ObjectValueDeserializer基类,在序列化对象时是标准 Jackson 类的可用替代,细节见其 API 文档。
用 @JacksonMixin 给已有类混入注解
Jackson 支持 mixin,可以把额外注解混入目标类已声明的注解。Spring Boot 的 Jackson 自动配置会扫描应用包中用@org.springframework.boot.jackson.JacksonMixin标注的类,并把它们注册到自动配置的JsonMapper,注册由org.springframework.boot.jackson.JacksonMixinModule完成。适合无法修改源码、又想调整某类序列化行为的情况。
完全替换默认的 JsonMapper
如果需要彻底接管,有两种方式:定义JsonMapper类型的@Bean,或者(如果更喜欢 builder 方式)定义tools.jackson.databind.json.JsonMapper.Builder类型的@Bean。
两点必须注意:
- 定义
JsonMapperBean 时,建议标注@Primary,因为它要替换的自动配置JsonMapper本身是@Primary的。 - 无论哪种方式,这样做都会禁用
JsonMapper的全部自动配置——前文的属性绑定、JacksonModule自动注册等都不会再作用于你定义的 Bean。
让定制生效于 HTTP 消息转换器
JsonMapper定制要影响 MVC/HTTP 客户端的 JSON 转换,还需要关注消息转换器这一层:
- 如果你提供任何
org.springframework.http.converter.json.JacksonJsonHttpMessageConverter类型的 Bean,它会替换 MVC 配置中的默认值。 - 也可以声明
org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizerBean 来添加转换器或覆盖某个默认转换器。
Spring Boot 文档中的示例展示了用自定义JsonMapper构建转换器并同时作用于服务端和客户端(完整代码见 MyHttpMessageConvertersConfiguration.java):
import java.text.SimpleDateFormat; import tools.jackson.databind.json.JsonMapper; import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer; import org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizer; import org.springframework.http.converter.HttpMessageConverters.ClientBuilder; import org.springframework.http.converter.HttpMessageConverters.ServerBuilder; import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; // 关键部分:构建自定义 JsonMapper 并贡献给 server 与 client JsonMapper jsonMapper = JsonMapper.builder() .defaultDateFormat(new SimpleDateFormat("yyyy-MM")) .build(); static class JacksonConverterCustomizer implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer { private final JsonMapper jsonMapper; JacksonConverterCustomizer(JsonMapper jsonMapper) { this.jsonMapper = jsonMapper; } @Override public void customize(ClientBuilder builder) { builder.withJsonConverter(new JacksonJsonHttpMessageConverter(this.jsonMapper)); } @Override public void customize(ServerBuilder builder) { builder.withJsonConverter(new JacksonJsonHttpMessageConverter(this.jsonMapper)); } }上面的JsonMapper是在定制器内部自行构建的,与自动配置的JsonMapper无关;这是与替换自动配置 Bean 不同的另一条控制路径,适合只希望替换 JSON 转换器所用 mapper 而不想关掉全部自动配置的场景。
从 Jackson 2 迁移:恢复旧的默认行为
对之前使用 Jackson 2 的应用,自动配置的JsonMapper可以设置为尽量接近 Spring Boot 当年为 Jackson 2 使用的默认值。把spring.jackson.use-jackson2-defaults设为true即可启用这些默认值,缓解迁移过程中的行为差异。
验证定制是否生效
验证路径就是请求一个 JSON 接口观察输出:Spring Boot 应用中的任何@RestController在 Jackson 3 位于 classpath 上时默认就渲染 JSON 响应,例如示例中的接口在http://localhost:8080/thing上直接返回对象的 JSON 表示。
- 设置了
spring.jackson.serialization.indent_output=true后,响应的 JSON 输出为格式化(pretty print)形式; - 注册了
@JacksonComponent序列化器后,对应类型的 JSON 结构应与你serialize方法写入的字段一致,反序列化方向可提交 JSON 请求验证能否按你的deserialize逻辑还原; - 如果预期看到 JSON 却在浏览器里看到 XML,这是浏览器倾向发送偏好 XML 的
Accept头导致的,换成Accept: application/json请求即可。
各路径的取舍:能用spring.jackson.*属性解决的不要写定制器;需要按条件编程式调整用JsonMapperBuilderCustomizer;行为与 Jackson 3 默认差异过大、短期不便调整时才用use-jackson2-defaults过渡;只有需要彻底接管时才定义JsonMapper或JsonMapper.BuilderBean,并记住它会禁用全部自动配置。更多细节可参考 JSON 特性文档和 Spring MVC 常见问题文档中的 "Customize the Jackson JsonMapper" 一节。
【免费下载链接】spring-bootSpring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.项目地址: https://gitcode.com/gh_mirrors/sp/spring-boot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考