Джексон: разобрать пользовательское смещение даты и времени
У меня есть модель, которая имеет свойство timestamp:
class Model {
@JsonProperty("timestamp")
private OffsetDateTime timestamp;
}
Формат отметки времени следующий:
2017-09-17 13:45:42.710576+02
OffsetDateTime
не может разобрать это:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Невозможно десериализовать значение типа
java.time.OffsetDateTime
из строки "2017-09-17 13:45:42.710576+02": текст "2017-09-17 13: 45: 42.710576 + 02" не может быть проанализирован по индексу 10
Как я могу это исправить?
1 ответ
Вы должны сказать Джексону, в каком формате дата. В основном у вас есть year-month-day
с последующим hour:minute:second.microseconds
и смещение с 2 цифрами (+02
). Итак, ваш шаблон будет:
@JsonProperty("timestamp")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSSx")
private OffsetDateTime timestamp;
Взгляните на все паттерны даты / времени для более подробного объяснения.
Если вы хотите сохранить то же смещение (+02
) в OffsetDateTime
не забудьте отрегулировать DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE
возможность false
,
Если эта опция установлена true
(в моих тестах) результат конвертируется в UTC (но фактически он конвертируется в любой часовой пояс, настроенный в Джексоне):
2017-09-17T11: 45: 42.710576Z
Если я настроен на false
смещение, используемое на входе, сохраняется:
2017-09-17T13: 45: 42,710576+02:00
Приведенный выше код работает точно с 6 цифрами после десятичной точки. Но если это количество варьируется, вы можете использовать дополнительные шаблоны, разделенные []
,
Пример: если на входе может быть 6 или 3 десятичных знака, я могу использовать pattern = "yyyy-MM-dd HH:mm:ss.[SSSSSS][SSS]x"
, Необязательные разделы [SSSSSS]
а также [SSS]
говорит парсеру рассмотреть 6 или 3 цифры.
Проблема с дополнительными шаблонами заключается в том, что при сериализации он печатает все шаблоны (поэтому он будет печатать долю секунды дважды: с 6 и 3 цифрами).
Другой альтернативой является создание пользовательских сериализаторов и десериализаторов (путем расширения com.fasterxml.jackson.databind.JsonSerializer
а также com.fasterxml.jackson.databind.JsonDeserializer
):
public class CustomDeserializer extends JsonDeserializer<OffsetDateTime> {
private DateTimeFormatter formatter;
public CustomDeserializer(DateTimeFormatter formatter) {
this.formatter = formatter;
}
@Override
public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
return OffsetDateTime.parse(parser.getText(), this.formatter);
}
}
public class CustomSerializer extends JsonSerializer<OffsetDateTime> {
private DateTimeFormatter formatter;
public CustomSerializer(DateTimeFormatter formatter) {
this.formatter = formatter;
}
@Override
public void serialize(OffsetDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
gen.writeString(value.format(this.formatter));
}
}
Затем вы можете зарегистрировать их в JavaTimeModule
, Как это настроить, будет зависеть от среды, которую вы используете (пример: в Spring вы можете настроить файлы xml). Я просто сделаю это программно в качестве примера.
Сначала я создаю форматтер, используя java.time.format.DateTimeFormatterBuilder
:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
// date/time
.appendPattern("yyyy-MM-dd HH:mm:ss")
// optional fraction of seconds (from 0 to 9 digits)
.optionalStart().appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).optionalEnd()
// offset
.appendPattern("x")
// create formatter
.toFormatter();
Этот форматер принимает необязательную долю секунды с 0 до 9 цифр. Затем я использую пользовательские классы выше и регистрирую их в ObjectMapper
:
// set formatter in the module and register in object mapper
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(OffsetDateTime.class, new CustomSerializer(formatter));
module.addDeserializer(OffsetDateTime.class, new CustomDeserializer(formatter));
mapper.registerModule(module);
Я также удаляю @JsonFormat
аннотация с поля:
@JsonProperty("timestamp")
private OffsetDateTime timestamp;
И теперь он принимает такие значения, как 2017-09-17 13:45:42+02
(без доли секунды) и 2017-09-17 13:45:42.71014+02
(5 десятичных цифр). Он может анализировать от 0 до 9 десятичных цифр (9 - максимум, поддерживаемый API) и печатает точно такое же количество при сериализации.
Вышеуказанная альтернатива очень гибкая, так как позволяет устанавливать форматер в пользовательских классах. Но он также устанавливает сериализацию и десериализацию для всех OffsetDateTime
поля.
Если вы этого не хотите, вы также можете создать класс с фиксированным форматированием:
static class CustomDeserializer extends JsonDeserializer<OffsetDateTime> {
private DateTimeFormatter formatter = // create formatter as above
// deserialize method is the same
}
static class CustomSerializer extends JsonSerializer<OffsetDateTime> {
private DateTimeFormatter formatter = // create formatter as above
// serialize method is the same
}
Затем вы можете добавить их только в те поля, которые вы хотите, используя аннотации com.fasterxml.jackson.databind.annotation.JsonSerialize
а также com.fasterxml.jackson.databind.annotation.JsonDeserialize
:
@JsonProperty("timestamp")
@JsonSerialize(using = CustomSerializer.class)
@JsonDeserialize(using = CustomDeserializer.class)
private OffsetDateTime timestamp;
При этом вам не нужно регистрировать пользовательские сериализаторы в модуле, и только аннотированное поле будет использовать пользовательские классы (другое OffsetDateTime
поля будут использовать настройки по умолчанию).