Джексон: разобрать пользовательское смещение даты и времени

У меня есть модель, которая имеет свойство 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 поля будут использовать настройки по умолчанию).

Другие вопросы по тегам