如何使用 Jackson 将未分区的日期时间反序列化为分区
How to deserialize unzoned datetime to zoned using Jackson
我正在寻找将未分区的日期时间反序列化的最简单方法,例如将 2021-03-31 00:00:00
反序列化为具有静态定义时区的 OffsetDateTime
。这些是来自第三方的 API 响应,它们不包括时区,但他们的文档声明所有时间戳都在特定区域中。
我认为 ObjectMapper.setTimeZone()
会起作用,但我没有任何运气。 Kotlin例子来说明:
data class Bar(
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
val timestamp: OffsetDateTime
)
@Test
fun `foo`() {
val mapper = ObjectMapper()
.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, true)
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
.setTimeZone(TimeZone.getTimeZone("America/Phoenix"))
val serialized = "{\"timestamp\":\"2019-01-01 00:00:00\"}"
val deserialized: Bar = mapper.readValue(serialized)
}
这会引发异常 Unable to obtain ZoneOffset from TemporalAccessor
。将字段类型更改为 LocalDateTime
有效。杰克逊不应该使用提供的时区进行上下文反序列化吗?
我知道我可以编写自定义反序列化器,但我希望这种更简单的方法能够发挥作用。
我认为自定义反序列化器或更改为 LocaleDateTime
是唯一的选择...
从com.fasterxml.jackson.datatype.jsr310.deser.InstantDesirializer
的实现可以看出,mapper的时区并没有进入java.time
类型的解析逻辑。
try {
TemporalAccessor acc = _formatter.parse(string);
value = parsedToValue.apply(acc); // creation of OffsetDateTime from the TemporarAccessor is done here
if (shouldAdjustToContextTimezone(context)) {
return adjust.apply(value, this.getZone(context));
}
} catch (DateTimeException e) {
value = _handleDateTimeException(context, e, string);
}
Jackson 的行为与以下内容类似:
val parsed: TemporalAccessor = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").parse("2019-01-01 00:00:00")
val result = OffsetDateTime.from(parsed) //Fails, but LocalDateTime.from(parsed) would have succeeded
//Time zone adjusting happens after that
所以,我相信,您必须为此编写自定义反序列化程序:
data class Bar(
@JsonDeserialize(using = OffsetDateTimeDeserializer::class)
val timestamp: OffsetDateTime
)
class OffsetDateTimeDeserializer(vc: Class<*>? = null) : StdDeserializer<OffsetDateTime>(vc) {
companion object {
private val formatter: DateTimeFormatter = DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss")
.parseDefaulting(
ChronoField.OFFSET_SECONDS,
//God bless Phoenix for not using DST
TimeZone.getTimeZone("America/Phoenix").rawOffset.toLong() / 1000 //Convert from milliseconds to seconds
)
.toFormatter()
}
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): OffsetDateTime =
OffsetDateTime.parse(p.valueAsString, formatter)
}
从其他答案看来,您需要构建自定义反序列化器。下面是我将如何在 Java 中进行解析。然后我相信你会封装一个 deseializer 并翻译成 Kotlin。
我先声明:
private static final DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
private static final ZoneId thirdPartyZone = ZoneId.of("Brazil/Acre");
填写已知的具体时区。那么转换为:
String valueAsString = "2021-03-31 00:00:00";
OffsetDateTime odt = LocalDateTime.parse(valueAsString, formatter)
.atZone(thirdPartyZone)
.toOffsetDateTime();
System.out.println(odt);
输出:
2021-03-31T00:00-05:00
此代码也适用于具有夏令时 (DST) 和其他时间异常的时区。
不要使用 TemporalAccessor
。它的文档说:
This interface is a framework-level interface that should not be
widely used in application code. Instead, applications should create
and pass around instances of concrete types, such as LocalDate
.
There are many reasons for this, part of which is that implementations
of this interface may be in calendar systems other than ISO. …
我正在寻找将未分区的日期时间反序列化的最简单方法,例如将 2021-03-31 00:00:00
反序列化为具有静态定义时区的 OffsetDateTime
。这些是来自第三方的 API 响应,它们不包括时区,但他们的文档声明所有时间戳都在特定区域中。
我认为 ObjectMapper.setTimeZone()
会起作用,但我没有任何运气。 Kotlin例子来说明:
data class Bar(
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
val timestamp: OffsetDateTime
)
@Test
fun `foo`() {
val mapper = ObjectMapper()
.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, true)
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
.setTimeZone(TimeZone.getTimeZone("America/Phoenix"))
val serialized = "{\"timestamp\":\"2019-01-01 00:00:00\"}"
val deserialized: Bar = mapper.readValue(serialized)
}
这会引发异常 Unable to obtain ZoneOffset from TemporalAccessor
。将字段类型更改为 LocalDateTime
有效。杰克逊不应该使用提供的时区进行上下文反序列化吗?
我知道我可以编写自定义反序列化器,但我希望这种更简单的方法能够发挥作用。
我认为自定义反序列化器或更改为 LocaleDateTime
是唯一的选择...
从com.fasterxml.jackson.datatype.jsr310.deser.InstantDesirializer
的实现可以看出,mapper的时区并没有进入java.time
类型的解析逻辑。
try {
TemporalAccessor acc = _formatter.parse(string);
value = parsedToValue.apply(acc); // creation of OffsetDateTime from the TemporarAccessor is done here
if (shouldAdjustToContextTimezone(context)) {
return adjust.apply(value, this.getZone(context));
}
} catch (DateTimeException e) {
value = _handleDateTimeException(context, e, string);
}
Jackson 的行为与以下内容类似:
val parsed: TemporalAccessor = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").parse("2019-01-01 00:00:00")
val result = OffsetDateTime.from(parsed) //Fails, but LocalDateTime.from(parsed) would have succeeded
//Time zone adjusting happens after that
所以,我相信,您必须为此编写自定义反序列化程序:
data class Bar(
@JsonDeserialize(using = OffsetDateTimeDeserializer::class)
val timestamp: OffsetDateTime
)
class OffsetDateTimeDeserializer(vc: Class<*>? = null) : StdDeserializer<OffsetDateTime>(vc) {
companion object {
private val formatter: DateTimeFormatter = DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss")
.parseDefaulting(
ChronoField.OFFSET_SECONDS,
//God bless Phoenix for not using DST
TimeZone.getTimeZone("America/Phoenix").rawOffset.toLong() / 1000 //Convert from milliseconds to seconds
)
.toFormatter()
}
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): OffsetDateTime =
OffsetDateTime.parse(p.valueAsString, formatter)
}
从其他答案看来,您需要构建自定义反序列化器。下面是我将如何在 Java 中进行解析。然后我相信你会封装一个 deseializer 并翻译成 Kotlin。
我先声明:
private static final DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
private static final ZoneId thirdPartyZone = ZoneId.of("Brazil/Acre");
填写已知的具体时区。那么转换为:
String valueAsString = "2021-03-31 00:00:00";
OffsetDateTime odt = LocalDateTime.parse(valueAsString, formatter)
.atZone(thirdPartyZone)
.toOffsetDateTime();
System.out.println(odt);
输出:
2021-03-31T00:00-05:00
此代码也适用于具有夏令时 (DST) 和其他时间异常的时区。
不要使用 TemporalAccessor
。它的文档说:
This interface is a framework-level interface that should not be widely used in application code. Instead, applications should create and pass around instances of concrete types, such as
LocalDate
. There are many reasons for this, part of which is that implementations of this interface may be in calendar systems other than ISO. …