Spring webtestclient 将日期序列化为时间戳而不是日期
Spring webtestclient serializes dates to timestamps instead of dates
我正在尝试检查我从 webtestclient 返回的数据是否与我期望的相同。但是当我将 Jackson 应用于 webtestclient 编解码器时,来自用户数据 class 的 ZonedDateTime 不仅显示为日期,还显示为时间戳。示例:2021-12-09T16:39:43.225207700+01:00
转换为 1639064383.225207700
,而我预计不会发生任何变化。有人可以解释我做错了什么。 (在测试之外调用此端点时使用此 jackson 配置给出日期而不是时间戳)
WebTestClientUtil:
object WebTestClientUtil {
fun webTestClient(routerFunction: RouterFunction<ServerResponse>): WebTestClient {
return WebTestClient
.bindToRouterFunction(routerFunction)
.configureClient()
.codecs { configurer: ClientCodecConfigurer ->
configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON))
configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON))
}
.build()
}
}
测试用例:
@Test
fun `get user when given correct data`() {
val user = GlobalMocks.mockedUser
coEvery { userRepository.getUserWithData(any()) } returns user
val result = webTestClient.get()
.uri("/api/v1/user/${user.userId}")
.exchange()
.expectStatus().is2xxSuccessful
.expectBody<Result>().returnResult().responseBody?.payload
assertEquals(user, result)
}
data class Result(
val payload: User
)
杰克逊配置:
class JacksonConfig {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
val objectMapper = jacksonObjectMapper().applyDefaultSettings()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}
编辑:还发现 这让我觉得它可能与 bindToRouterFunction
.
有关
您需要定义一个 ObjectMapper
bean 以便不使用自动配置的 bean:
@Configuration(proxyBeanMethods = false)
class JacksonConfiguration {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
}
@Bean
fun objectMapper() = jacksonObjectMapper().applyDefaultSettings ()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}
我正在尝试检查我从 webtestclient 返回的数据是否与我期望的相同。但是当我将 Jackson 应用于 webtestclient 编解码器时,来自用户数据 class 的 ZonedDateTime 不仅显示为日期,还显示为时间戳。示例:2021-12-09T16:39:43.225207700+01:00
转换为 1639064383.225207700
,而我预计不会发生任何变化。有人可以解释我做错了什么。 (在测试之外调用此端点时使用此 jackson 配置给出日期而不是时间戳)
WebTestClientUtil:
object WebTestClientUtil {
fun webTestClient(routerFunction: RouterFunction<ServerResponse>): WebTestClient {
return WebTestClient
.bindToRouterFunction(routerFunction)
.configureClient()
.codecs { configurer: ClientCodecConfigurer ->
configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON))
configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON))
}
.build()
}
}
测试用例:
@Test
fun `get user when given correct data`() {
val user = GlobalMocks.mockedUser
coEvery { userRepository.getUserWithData(any()) } returns user
val result = webTestClient.get()
.uri("/api/v1/user/${user.userId}")
.exchange()
.expectStatus().is2xxSuccessful
.expectBody<Result>().returnResult().responseBody?.payload
assertEquals(user, result)
}
data class Result(
val payload: User
)
杰克逊配置:
class JacksonConfig {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
val objectMapper = jacksonObjectMapper().applyDefaultSettings()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}
编辑:还发现 bindToRouterFunction
.
您需要定义一个 ObjectMapper
bean 以便不使用自动配置的 bean:
@Configuration(proxyBeanMethods = false)
class JacksonConfiguration {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
}
@Bean
fun objectMapper() = jacksonObjectMapper().applyDefaultSettings ()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}