如何将具有偏移量(“ 2019-01-29 + 01:00”)的日期反序列化为与`java.time`相关的类?

时间:2019-01-29 15:08:58

标签: kotlin java-8 jackson jsr310 java.time

我已经在Spring Boot(2.1.2)系统中重构了一些旧代码,并从java.util.Date迁移到了基于java.time的类(jsr310)。系统期望日期采用ISO8601格式的字符串,而有些则是带有时间信息的完整时间戳(例如"2019-01-29T15:29:34+01:00"),而有些则只是带有偏移量的日期(例如"2019-01-29+01:00")。这是DTO(作为Kotlin数据类):

data class Dto(
        // ...
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
        @JsonProperty("processingTimestamp")
        val processingTimestamp: OffsetDateTime,

        // ...
        @JsonFormat(shape = JsonFormat.Shape.STRING,  pattern = "yyyy-MM-ddXXX")
        @JsonProperty("orderDate")
        val orderDate: OffsetDateTime,
        // ...
)

Jackson完美地反序列化processingTimestamp时,却失败了,并显示orderDate

Caused by: java.time.DateTimeException: Unable to obtain OffsetDateTime from TemporalAccessor: {OffsetSeconds=32400},ISO resolved to 2018-10-23 of type java.time.format.Parsed
    at java.time.OffsetDateTime.from(OffsetDateTime.java:370) ~[na:1.8.0_152]
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:207) ~[jackson-datatype-jsr310-2.9.8.jar:2.9.8]

这对我来说很有意义,因为OffsetDateTime找不到构成即时信息所需的任何时间信息。如果我更改为val orderDate: LocalDate,杰克逊可以成功反序列化,但是偏移信息就消失了(以后需要转换为Instant)。

问题

我当前的解决方法是将OffsetDateTime与自定义解串器结合使用(请参见下文)。 但是我想知道,是否有更好的解决方案?

我也希望使用更合适的数据类型,例如 OffsetDate ,但我无法在java.time中找到它。

PS

我在问自己“ 2019-01-29 + 01:00”是否对ISO8601有效。但是,由于我发现java.time.DateTimeFormatter.ISO_DATE是可以正确解析它的,并且我不能更改客户端发送数据的格式,所以我抛开了这个问题。

解决方法

data class Dto(
        // ...
        @JsonFormat(shape = JsonFormat.Shape.STRING,  pattern = "yyyy-MM-ddXXX")
        @JsonProperty("catchDate")
        @JsonDeserialize(using = OffsetDateDeserializer::class)
        val orderDate: OffsetDateTime,
        // ...
)

class OffsetDateDeserializer(
        private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_DATE
) : JSR310DateTimeDeserializerBase<OffsetDateTime>(OffsetDateTime::class.java, formatter) {


    override fun deserialize(parser: JsonParser, context: DeserializationContext): OffsetDateTime? {
        if (parser.hasToken(JsonToken.VALUE_STRING)) {
            val string = parser.text.trim()
            if (string.isEmpty()) {
                return null
            }

            val parsed: TemporalAccessor = formatter.parse(string)
            val offset = if(parsed.isSupported(ChronoField.OFFSET_SECONDS)) ZoneOffset.from(parsed) else ZoneOffset.UTC
            val localDate = LocalDate.from(parsed)
            return OffsetDateTime.of(localDate.atStartOfDay(), offset)
        }
        throw context.wrongTokenException(parser, _valueClass, parser.currentToken, "date with offset must be contained in string")
    }

    override fun withDateFormat(otherFormatter: DateTimeFormatter?): JsonDeserializer<OffsetDateTime> = OffsetDateDeserializer(formatter)
}

1 个答案:

答案 0 :(得分:0)

正如@JodaStephen在评论中解释的那样,{em> OffsetDate 不包含在java.time中,因为它具有最少的类集。因此,OffsetDateTime是最佳选择。

他还建议使用DateTimeFormatterBuilderparseDefaulting创建一个DateTimeFormatter实例,从格式化程序的解析结果(OffsetDateTime)中直接创建TemporalAccessor。 AFAIK,我仍然需要创建一个自定义解串器以使用格式化程序。这是代码,它解决了我的问题:

class OffsetDateDeserializer: JsonDeserializer<OffsetDateTime>() {

    private val formatter = DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_DATE)
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
            .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
            .parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)
            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
            .toFormatter()


    override fun deserialize(parser: JsonParser, context: DeserializationContext): OffsetDateTime? {
        if (parser.hasToken(JsonToken.VALUE_STRING)) {
            val string = parser.text.trim()
            if (string.isEmpty()) {
                return null
            }
            try {
                return OffsetDateTime.from(formatter.parse(string))
            } catch (e: DateTimeException){
                throw context.wrongTokenException(parser, OffsetDateTime::class.java, parser.currentToken, "error while parsing date: ${e.message}")
            }
        }
        throw context.wrongTokenException(parser, OffsetDateTime::class.java, parser.currentToken, "date with offset must be contained in string")
    }
}