Как вручную использовать объект отображения по умолчанию пружин / десериализацию Http-запроса тела к объекту вручную - PullRequest
0 голосов
/ 28 октября 2019

У меня есть универсальный Rest-Constroller, который должен создать сущность из имени сущности - тело запроса, представляющее свойства сущностей. Мне нужно вручную сопоставить тело запроса с целевым классом.

Я уже пробовал разные способы настроить способ, которым мой RestController десериализует данные, и считаю, что мой текущий подход является самым простым. Теперь я регистрирую @PostMapping и извлекаю entityName, который я использую для определения правильного класса, и сущность @RequestBody StringJJ, которую я преобразую через ObjectMapper в определенный класс.

, но при десериализации LocalDate у меня возникают проблемы (Примечание: мои строки даты имеют формат 'гггг-ММ-дд'Т'ЧЧ: мм: сс.СССЗ', где времена нули).

Я использую Springboot 2.1.6.RELEASE с jpa 2.1.9 ипоэтому не требуется настраиваемая обработка свойств LocalDate / LocalDateTime в моих RestControllers, если я использую параметр сущности @RequestBody XXXEntity. Но при внедрении компонента OnjectMapper в мой RestController и попытке десериализации objectMapper.readValue (entityJson, entityClass) происходит сбой в свойствах LocalDate.

Как я могу десериализовать способ, которым Spring делает (правильно), если класс сущности известен заранее?

1.) Исключение без пользовательской конфигурации: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Невозможно создать экземпляр java.time.LocalDate (не существует создателей, таких как конструкция по умолчанию): нет конструктора String-аргумент/ factory метод для десериализации из строкового значения ('2019-10-29T00: 00: 00.000Z')

2.) При использовании пользовательского JSR310Module ObjecktMapper

java.time.format.DateTimeParseException: Текст '2019-10-29T00: 00: 00.000Z' не может быть проанализирован, найден неразобранный текст по индексу 10

        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
                .serializationInclusion(JsonInclude.Include.NON_NULL)
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 
                .modules(new JSR310Module())
                .build();
Same error with custom DateFormat:
  SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
        objectMapper.setDateFormat(df);

Я попытался зарегистрировать десериализатор LocalDateTime для LocalDate das well:

 Jackson2ObjectMapperBuilder.json()
    .deserializerByType(LocalDate.class, LocalDateTimeDeserializer.INSTANCE)

Результат улучшается ... поэтому не удается выполнить очистку, когда я нажимаю часовой пояс UTC: java.time.format.DateTimeParseException: текст '2019-10-29T00: 00: 00.000Z' не может быть проанализирован, найден неразобранный текст с индексом 23

Вот так выглядит мой контроллер:

    @Autowired
    public AdminRestController(EntityService entityService, ObjectMapper objectMapper) {
        this.entityService = entityService;
        this.objectMapper = objectMapper;
    }

...

    @Transactional
    @PostMapping(value = "/entities/{entityName}")
    public List<T> createEntity(@PathVariable String entityName, @RequestBody String entityJson) {
        T newEntity = constructEntity(entityName, entityJson);
        entityService.create(newEntity);
        ...
    }
...
    private T constructEntity(String entityName, String entityJson) {
        Class<T> entityClass;
        try {
            entityClass = findClassByName(entityName);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("Entity " + entityName + " unknwon.");
        }
        try {
            T entity = objectMapper.readValue(entityJson, entityClass);
            return entity;
        } catch (IOException e) {
            throw new IllegalStateException("Unable to construct entity  " + entityName + " from provided data.", e);
        }
    }

Ответы [ 2 ]

1 голос
/ 29 октября 2019

Вы можете считать это таким же хакерским. Моя версия логики была бы:

    if (string.isEmpty()) {
        return null;
    }
    if (string.endsWith("Z")) {
        return Instant.parse(string).atZone(ZONE_SWISS).toLocalDate();
    }
    return LocalDate.parse(string, DateTimeFormatter.ISO_LOCAL_DATE_TIME);

Я избежал изменения строки и преобразования из LocalDateTime в LocalDate. Кроме того, поскольку Z может стоять только последним в строке, моя версия дает более точную проверку. Наконец, для вкуса мне нравится метод String.isEmpty.

1 голос
/ 28 октября 2019

У меня есть «решение» для моей проблемы ... но оно кажется хакерским ...

Я отследил свою проблему до одного корневого источника: - мои клиенты всегда отправляют timezoned-utc-строки как датынапример, «2019-10-29T00: 00: 00.000Z» (включая информацию о времени) вместо «реальных» строк даты. - Как ни странно, springboot по умолчанию (по крайней мере, с spring-boot-starter-web и т. Д.) Обрабатывает это правильно, если такие строки встречаются с атрибутом LocalDate при сериализации. - Если я попытаюсь вручную перестроить это поведение с помощью Jacksons ObjectMapper, я не смогу решить обе проблемы одновременно и получу сообщение об ошибке времени или информации о часовом поясе.

Что помогает, например, комментирует мойPojo с пользовательским десериализатором:

    @JsonDeserialize(using = LocalDateTimeStringToLocalDateDeserializer.class)
    private LocalDate validFrom;
/*
 * Copyright 2013 FasterXML.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the license for the specific language governing permissions and
 * limitations under the license.
 */

package ch.acrevison.tpl.findata.util.serializing;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

/**
 * Deserializer for Java 8 temporals
 * 1.) parses string as LocalDateTime if it is longer as an expected iso date.
 * 1.a ) if utc timezone marker is contained>
 */
public class LocalDateTimeStringToLocalDateDeserializer extends JsonDeserializer<LocalDate> implements java.io.Serializable {

    private static ZoneId ZONE_SWISS = ZoneId.of("Europe/Zurich");

    @Override
    public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException {

        String string = parser.getText().trim();
        if (string.length() == 0) {
            return null;
        }

        if (string.endsWith("Z")) {
            ZonedDateTime utcZonedDateTime = ZonedDateTime.parse(string);
            ZonedDateTime localDateTime = utcZonedDateTime.withZoneSameInstant(ZONE_SWISS);
            return localDateTime.toLocalDate();
        } else if (string.length() > 10) {
            return LocalDate.parse(string, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        } else {
            return LocalDate.parse(string);
        }
    }
}

Нет лучших идей?

...