Могу ли я получить атрибут даты из ответа остального, как LocalDateTime или LocalDate в моем приложении?

У меня есть этот класс:

public class MyLocalApplicationClass {
    private String name
    private LocalDateTime creationDate;
    private String createdBy;
}

И в ответ, который я получаю от службы отдыха, я получаю этот объект (в виде json):

public class MyRemoteApplicationClass {
    private String name
    private Date creationDate;
    private String createdBy;
}

Поэтому, когда я отправляю запрос, я получаю значение (json) createDate из MyRemoteApplicationClass следующим образом:

{
    “name”:”anything”,
    "creation_date": 1666190973000,
    "created_by": “anyone”
}

Поэтому мне было интересно, можно ли получить это как LocalDateTime или я должен получить его как Date, а затем перевести его в LocalDateTime (думаю, это мой лучший вариант)?

Потому что я пытаюсь получить его как LocalDateTime, но выдает эту ошибку:

raw timestamp (1656015404000) not allowed for 
java.time.LocalDateTime: need additional information such as an
offset or time-zone (see class Javadocs)

Кроме того, я пытался получить его только как LocalDate, но он выдает эту ошибку (которую я уже добавил зависимость от jsr310, а также добавил сериализатор и десериализатор с этими аннотациями @JsonSerialize(using = LocalDateSerializer.class) @JsonDeserialize(using = LocalDateDeserializer.class), поэтому я думаю, что настоящая проблема заключается в предупреждении) и предупреждение:

Java 8 date/time type `java.time.DateTimeException` not supported
by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
to enable handling (through reference chain:
com.fasterxml.jackson.databind.JsonMappingException["cause"])

WARN -- com.fasterxml.jackson.databind.JsonMappingException:
Invalid value for EpochDay (valid values -365243219162 - 365241780471): 1656015404000
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

TL;DR

Есть несколько возможных решений:

  • Получите метку времени как есть и выполните синтаксический анализ в конструкторе;
  • Примените глобальные конфигурации, зарегистрировав JavaTimeModule и применив требуемую точность отметки времени.
  • Реализуйте пользовательский десериализатор.

Давайте пройдемся по этим вариантам.

Разобрать метку времени внутри конструктора

Мы можем объявить конструктор всех аргументов внутри MyLocalApplicationClass и аннотировать каждый аргумент @JsonProperty. Параметр contranctior creationDate должен быть получен как long, так как он будет проанализирован от миллисекунды эпохи до LocalDateTime вручную.

Вот как это может выглядеть:

public class MyLocalApplicationClass {
    private String name;
    private LocalDateTime creationDate;
    private String createdBy;
    
    public MyLocalApplicationClass(@JsonProperty("name") String name,
                                   @JsonProperty("creation_date") long creationDate,
                                   @JsonProperty("created_by") String createdBy) {
        this.name = name;
        this.createdBy = createdBy;
        this.creationDate = Instant
            .ofEpochMilli(creationDate)
            .atZone(ZoneOffset.UTC)
            .toLocalDateTime();
    }
}

Применить глобальные конфигурации

Другим вариантом может быть применение глобальных конфигураций десериализации.

Но для этого потребуется изменить тип свойства creationDate на Instant.

Чтобы правильно настроить ObjectMapper, нам нужно сделать два шага:

  • зарегистрировать JavaTimeModule модуль;
  • сообщить ObjectMapper о точности метки времени, установив для свойства десериализации DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS значение false.

Для этого мы можем поместить Jackson2ObjectMapperBuilder и ObjectMapper как бобы в контекст Spring.

@Configuration
public class JsonConfig {
    
    @Bean
    public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
        return new Jackson2ObjectMapperBuilder();
    }

    @Bean
    public ObjectMapper objectMapper() {
        return jackson2ObjectMapperBuilder()
            .build()
            .registerModule(new JavaTimeModule())
            .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
    }
}

Обратите внимание, что creationDate должен быть типа Instant, аннотации привязки данных в этом случае нужны только потому, что имена свойств и полей не совпадают (они не связаны с преобразованием).

public class MyLocalApplicationClass {
    private String name;
    @JsonProperty("creation_date")
    private Instant creationDate;
    @JsonProperty("created_by")
    private String createdBy;
    
    // getter, setters, etc.
}

Упрощенная глобальная конфигурация

Как @M. Deinum указал в комментариях, требуемую точность можно настроить с помощью application.properties:

spring.jackson.deserialization.READ_DATE_TIMESTAMPS_AS_NANOSECONDS=false

И мы можем удалить оба компонента (сопоставитель и построитель сопоставителя), объявленные в классе конфигурации в приведенном выше примере, потому что JacksonAutoConfiguration захватит и зарегистрирует все известные модули при запуске приложения.

Объявление MyLocalApplicationClass будет идентично показанному выше (creationDate имеет тип java.time.Instant).

Создайте собственный десериализатор

Наконец, мы можем реализовать собственный десериализатор, расширив StdDeserializer и переопределив его abstract метод deserialize().

public class DateTimeDeserializer extends StdDeserializer<LocalDateTime> {
    
    public DateTimeDeserializer() {
        super(LocalDateTime.class);
    }
    
    @Override
    public LocalDateTime deserialize(JsonParser p,
                                     DeserializationContext ctxt) throws IOException, JacksonException {
    
    
        JsonNode node = p.getCodec().readTree(p);
        long timestamp = node.longValue();
        
        return Instant
            .ofEpochMilli(timestamp)
            .atZone(ZoneOffset.UTC)
            .toLocalDateTime();
    }
}

Чтобы дать Джексону указание применить этот десериализатор, его можно указать с помощью свойства using аннотации @JsonDeserialize, которая должна быть размещена поверх поля creationDate.

public class MyLocalApplicationClass {

    private String name;

    @JsonDeserialize(using = DateTimeDeserializer.class)
    @JsonProperty("creation_date")
    private LocalDateTime creationDate;

    @JsonProperty("created_by")
    private String createdBy;
    
    // getter, setters, etc.
}

Jackson2ObjectMapperBuilder автоматически определяет JavaTimeModule, что вам не нужно добавлять. Вы также можете установить spring.jackson.deserialization.READ_DATE_TIMESTAMPS_AS_NANOS‌​ECONDS=false, а затем полностью отказаться от конфигурации.

M. Deinum 15.11.2022 13:39

@ M.Deinum Спасибо, это ценное предложение. Я знаю, что модули могут быть объявлены как зарегистрированные автоматически, но я не знал, что точность метки времени можно настроить в свойствах приложения. Я улучшил ответ и включил эту информацию.

Alexander Ivanchenko 16.11.2022 15:19

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

M. Deinum 16.11.2022 16:43

@M.Deinum Спасибо, спасибо, ага момент ... Итак, я неправильно прочитал документацию «авторегистрация для всех bean-компонентов модуля» (третий пункт), только пользовательские модули должны быть объявлены как bean-компоненты, и все стандартные модули, поставляемые Джексоном, будут обнаружены автоматически, верно?

Alexander Ivanchenko 16.11.2022 17:03

Не только стандартные модули, поставляемые Джексоном, но и другие известные модули также зарегистрированы (например, модуль гибернации).

M. Deinum 17.11.2022 07:52

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