Как десериализовать даты со смещением ("2019-01-29 + 01: 00") в классы, связанные с java.time? - PullRequest
0 голосов
/ 29 января 2019

Я произвел рефакторинг некоторого устаревшего кода в системе 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,
        // ...
)

Хотя Джексон отлично десериализует 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 голосов
/ 30 января 2019

Как объяснил @JodaStephen в комментариях, OffsetDate не было включено в java.time, чтобы иметь минимальный набор классов.Итак, OffsetDateTime - лучший вариант.

Он также предложил использовать DateTimeFormatterBuilder и parseDefaulting для создания 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")
    }
}

...