Русский
Русский
English
Статистика
Реклама

Кастомная (де) сериализация даты и времени в Spring

Представьте довольно распространённую ситуацию: ваше приложение взаимодействует с клиентами, которые находятся в разных часовых поясах. Вам часто приходится работать с датами, а для корректной работы системы, они передаются с указанием часового пояса отправителя. При этом, вам нужно:

  1. При получении запроса привести дату к серверному времени и работать с ней, а также сохранять в базу данных в таком виде

  2. В ответ возвращать дату и время с указанием серверного часового пояса

Для решения этой задачи Spring предоставляет удобный механизм для написания кастомной сериализации и десериализации. Главным его преимуществом является возможность вынести преобразования дат (и других типов данных) в отдельный конфигурационный класс и не вызывать методы преобразования каждый раз в исходном коде.

Десериализация

Для того, чтобы Spring понимал, что именно наш класс нужно использовать для (де)сериализации, его необходимо пометить аннотацией @JsonComponent

Ну а чтобы код был максимально кратким, я буду использовать внутренний статический класс, который должен быть унаследован от JsonDeserializerи параметризован нужным нам типом данных. Поскольку JsonDeserializerэто абстрактный класс, нам необходимо переопределить его абстрактный метод deserialize()

@JsonComponentpublic class CustomDateSerializer {        public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {            @Override        public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {            return null;        }    }}

Из параметров метода получаем переданную клиентом строку, проверяем её на null и получаем из неё объект класса ZonedDateTime

public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {    String date = jsonParser.getText();    if (date.isEmpty() || isNull(date) {        return null;    }    ZonedDateTime userDateTime = ZonedDateTime.parse(date);}

Для получения разницы во времени, у переменной userDateTime нужно вызвать метод withZoneSameInstant() и передать в него текущую серверную таймзону. Нам остаётся лишь преобразовать полученную дату кLocalDateTime

Для надежности, предположим, что нам может прийти дата без таймзоны, либо вообще некорректные данные. Для этого случая, добавим обработку исключений. Полностью десериализатор будет выглядеть так

public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {    @Override    public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {        String date = jsonParser.getText();        if (date.isEmpty()) {            return null;        }        try {            ZonedDateTime userDateTime = ZonedDateTime.parse(date);            ZonedDateTime serverTime = userDateTime.withZoneSameInstant(ZoneId.systemDefault());            return serverTime.toLocalDateTime();        } catch (DateTimeParseException e) {            try {                return LocalDateTime.parse(date);            } catch (DateTimeParseException ex) {                throw new IllegalArgumentException("Error while parsing date", ex);            }        }    }}

Предположим, что серверное времяUTC+03. Таким образом, когда клиент передаёт дату 2021-01-21T22:00:00+07:00, в нашем контроллере мы уже можем работать с серверным временем

public class Subscription {    private LocalDateTime startDate;      // standart getters and setters}
@RestController public class TestController {    @PostMapping  public void process(@RequestBody Subscription subscription) {    // к этому моменту поле startDate объекта subscription будет равно 2021-01-21T18:00  }}

Сериализация

С сериализацией алгоритм действий похожий. Нам нужно унаследовать класс от JsonSerializer, параметризовать его и переопределить абстрактный метод serialize()

Внутри метода мы проверим нашу дату на null, добавим к ней серверную таймзону и получим из неё строку. Остаётся лишь отдать её клиенту

public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {    @Override    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {        if (isNull(localDateTime)) {            return;        }        OffsetDateTime timeUtc = localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()));        jsonGenerator.writeString(timeUtc.toString());    }}

Круто? Можно в прод? Не совсем. В целом, этот код будет работать, но могут начаться проблемы, если серверная таймзона будет равна UTC+00. Дело в том, что конкретно для этого часового пояса id таймзоны отличается от стандартного формата. Посмотрим в документацию класса ZoneOffset

Таким образом, имея серверную таймзону UTC+03, на выходе мы получим строку следующего вида: 2021-02-21T18:00+03:00.Но если же оно UTC+00, то получим 2021-02-21T18:00Z

Поскольку мы работаем со строкой, нам не составит труда немного изменить код, дабы на выходе мы всегда получали дату в одном формате. Объявим две константы одна из них будет равна дефолтному id UTC+00, а вторая которую мы хотим отдавать клиенту, и добавим проверку - если серверное время находится в нулевой таймзоне, то заменим Z на +00:00. В итоге наш сериализотор будет выглядеть следующим образом

public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {    private static final String UTC_0_OFFSET_ID = "Z";    private static final String UTC_0_TIMEZONE = "+00:00";    @Override    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {        if (!isNull(localDateTime)) {            String date;            OffsetDateTime timeUtc = localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()));            if (UTC_0_OFFSET_ID.equals(timeUtc.getOffset().getId())) {                date = timeUtc.toString().replace(UTC_0_OFFSET_ID, UTC_0_TIMEZONE);            } else {                date = timeUtc.toString();            }            jsonGenerator.writeString(date);        }    }}

Итого

Благодаря встроенным механизмам спринга мы получили возможность автоматически конвертировать дату и время в требуемом формате, без каких-либо явных вызовов методов в коде

Полностью исходный код можно посмотреть здесь

Источник: habr.com
К списку статей
Опубликовано: 24.02.2021 18:11:35
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Java

Spring

Spring-boot

Date

Json

Serialization

Deserialization

Maven

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru