Spring Boot中OffsetDateTime响应被序列化为BigDecimal问题排查
我有一个包含OffsetDateTime类型time属性的SeriesListInner实例,将其作为Response Entity返回时,预期序列化为ISO格式字符串,但实际被序列化为BigDecimal类型的时间戳。实体类和控制器映射都是通过OpenAPI生成器自动生成的,无法添加Jackson注解自定义序列化逻辑,且此前功能正常,未修改过相关配置Bean或应用属性。
生成的实体类
/** * * @param id * @param name * @param episodes * @param languages * @param time * @param imagePath Relative path to where the image is located */ data class SeriesListInner( @get:JsonProperty("id", required = true) val id: kotlin.Long, @get:JsonProperty("name", required = true) val name: kotlin.String, @get:JsonProperty("episodes", required = true) val episodes: kotlin.Int, @get:JsonProperty("languages", required = true) val languages: kotlin.collections.List<kotlin.String>, @get:JsonProperty("time", required = true) val time: java.time.OffsetDateTime, @get:JsonProperty("imagePath") val imagePath: kotlin.String? = null ) { }
预期响应
[ { "id" : 1, "name" : "Series #1", "episodes" : 2, "languages" : [ ], "time" : "2023-07-26T01:16:11.789747Z", "imagePath" : null }, { "id" : 2, "name" : "Series #2", "episodes" : 2, "languages" : [ ], "time" : "2023-07-26T01:16:11.789747Z", "imagePath" : null } ]
实际响应
[ { "id" : 1, "name" : "Series #1", "episodes" : 2, "languages" : [ ], "time" : 1.690334171789747E9, "imagePath" : null }, { "id" : 2, "name" : "Series #2", "episodes" : 2, "languages" : [ ], "time" : 1.690334171789747E9, "imagePath" : null } ]
现有配置
Web配置类
@Configuration @EnableWebMvc class WebConfiguration : WebMvcConfigurer { override fun addCorsMappings(registry: CorsRegistry) { registry.addMapping("/**") } }
应用配置文件(application.yml)
spring: datasource: url: jdbc:postgresql://${PG_HOST:localhost/postgres} username: ${PG_USERNAME:postgres} password: ${PG_PASSWORD:postgres} jackson: serialization: write-dates-as-timestamps: false write-durations-as-timestamps: false api: data: config: default: resultLimit: 10 media: config: baseUrl: ${MEDIA_URL:http://localhost:8888/media}
Gradle依赖
dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") { exclude(group = "ch.qos.logback", module = "logback-classic") } implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-websocket") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.module:jackson-module-parameter-names") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") // implementation("com.fasterxml.jackson.datatype:jackson-module-kotlin") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8") implementation("org.jetbrains.kotlin:kotlin-reflect") // OpenAPI implementation("com.squareup.okhttp3:okhttp:4.11.0") implementation("org.openapitools:openapi-generator-gradle-plugin:6.6.0") compileOnly("org.projectlombok:lombok") runtimeOnly("org.postgresql:postgresql") annotationProcessor("org.projectlombok:lombok") itestImplementation("org.springframework.boot:spring-boot-starter-test") itestImplementation("org.testcontainers:junit-jupiter") itestImplementation("org.testcontainers:postgresql") itestImplementation("io.rest-assured:rest-assured:5.3.1") itestImplementation("org.testcontainers:testcontainers:1.18.3") itestImplementation("org.testcontainers:postgresql:1.18.3") // itestImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8") }
调试发现
调试Spring Boot的InstantSerializerBase类时发现,默认序列化器中WRITE_DATE_AS_TIMESTAMPS和WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS特性仍处于启用状态,尽管已在应用属性中配置关闭这些特性。尝试清理构建、重新克隆仓库均未解决问题。
解决方案
1. 移除@EnableWebMvc或手动配置Jackson
使用@EnableWebMvc会禁用Spring Boot自动配置的Jackson设置,导致application.yml中的jackson配置不生效。可以:
方案A:移除@EnableWebMvc
直接删掉配置类上的@EnableWebMvc注解,Spring Boot会自动应用Jackson的配置。方案B:保留@EnableWebMvc并手动配置Jackson
在配置类中添加Jackson2ObjectMapperBuilder的Bean,手动指定序列化特性:@Configuration @EnableWebMvc class WebConfiguration : WebMvcConfigurer { override fun addCorsMappings(registry: CorsRegistry) { registry.addMapping("/**") } @Bean fun jackson2ObjectMapperBuilder(): Jackson2ObjectMapperBuilder { return Jackson2ObjectMapperBuilder() .featuresToDisable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS ) .modulesToInstall( JavaTimeModule(), KotlinModule(), Jdk8Module(), ParameterNamesModule() ) } }
2. 配置OpenAPI生成器使用Java8日期库
确保OpenAPI生成器生成的类使用正确的日期序列化逻辑,在Gradle的OpenAPI生成配置中指定dateLibrary为java8:
openApiGenerate { // 其他配置(如spec文件路径等) configOptions = [ dateLibrary: "java8" ] }
3. 检查并解决Jackson依赖冲突
确保所有Jackson相关依赖版本与Spring Boot版本一致,避免因版本不一致导致的配置失效。可以通过Gradle的dependencyInsight命令检查:
./gradlew dependencyInsight --dependency jackson-datatype-jsr310
4. 手动注册JavaTimeModule并禁用时间戳序列化
如果自动配置仍未生效,手动注册JavaTimeModule并禁用相关特性:
@Configuration class JacksonConfig { @Bean fun objectMapper(): ObjectMapper { val mapper = ObjectMapper() val javaTimeModule = JavaTimeModule() mapper.registerModule(javaTimeModule) mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) mapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) return mapper } }
内容的提问来源于stack exchange,提问作者Oscar Reyes

