Джексон сериализует, используя @JsonProperty name в конструкторе

#java #spring #spring-boot #jackson

#java #весна #весенняя загрузка #джексон

Вопрос:

Я работал над обновлением версии Spring Boot одного из моих микросервисов и наткнулся на странное поведение. У меня есть такой класс:

 public class FilteredData {

    private final List<ShipmentData> shipments;

    public FilteredData(@JsonProperty("listShipments") List<ShipmentData> shipments) {
        this.shipments = shipments;
    }

    public List<ShipmentData> getShipments() {
        return shipments;
    }
}
  

Поведение, которое я имел перед выполнением обновления, заключалось в том, что при десериализации имя listShipments использовалось в объекте JSON для сопоставления со shipments свойством класса Java. Однако при сериализации shipments свойство будет записываться с именем shipments , а не listShipments .
Проблема в том, что теперь он использует имя listShipments как при десериализации, так и при сериализации. Я не уверен, в какой момент эта проблема начала возникать, поскольку моя первоначальная версия Spring Boot была 1.5.7, и я медленно обновляю ее до 2.3.4. Но я считаю, что это начало происходить после версии 2.0.0.

Я не знаю, вызвано ли это каким-то внутренним изменением в автоконфигурации Джексона Spring Boot или изменением в самой библиотеке Jackson, но мне трудно отследить, что вызвало это и как это исправить.

РЕДАКТИРОВАТЬ: я заметил, что с последней версии Spring Boot 1 (1.5.22) до Spring Boot 2.0.0 была изменена младшая версия Jackson (с 2.8 до 2.9). Могло ли это вызвать проблему?

Ответ №1:

Я смог воспроизвести это точное поведение при переключении с Spring Boot 1.5.7.RELEASE на 2.0.0.RELEASE. Сначала я добавил родительский элемент и зависимость spring-boot-starter-web. Затем я создал простой @RestController , использовал предоставленный вами POJO и ShipmentData заменил String его. Когда я вручную создаю Jackson ObjectMapper , я не смог воспроизвести проблему в двух выпусках.

Самая интересная часть: когда вы переименовываете имя параметра конструктора во что-то другое shipments , кажется, что оно работает нормально. Возможно, это может быть ошибка в Jackson или где-то в Spring Framework, которая проявляется только тогда, когда средство получения совпадает с именем параметра в конструкторе.

Я предлагаю использовать @JsonAlias для определения альтернативного имени, которое должно быть принято во время десериализации:

 @JsonCreator
public FilteredData(
        @JsonProperty("shipments")
        @JsonAlias("listShipments")
                List<ShipmentData> shipments) {
    this.shipments = shipments;
}
  

При этом вы можете десериализовать с помощью обоих listShipments и shipments в то время как используется только для сериализации shipments (определяется getter).


Для справки, рабочая реализация с 2.3.3.RELEASE:

 @RestController
public class FilteredDataController {

    @PostMapping("/data")
    public FilteredData postFilteredData(@RequestBody FilteredData data) {
        return data;
    }
}
  
 public class FilteredData {
    
    private final List<String> shipments;

    @JsonCreator
    public FilteredData(
            @JsonProperty("shipments") @JsonAlias("listShipments") 
                    List<String> shipments) {
        this.shipments = shipments;
    }

    public List<String> getShipments() {
        return shipments;
    }
}
  

Запрос:

 curl -d '{"listShipments":["a","b","c"]}' -H 'Content-Type: application/json' http://localhost:8080/data
  

Результат:

 {"shipments":["a","b","c"]}
  

Ответ №2:

Мне удалось найти проблему! Это было вызвано зависимостью, используемой spring-boot-starter-web called jackson-module-parameter-names . Эта библиотека предоставляет модуль Jackson с именем ParameterNamesModule , который, согласно автоконфигурации Jackson Spring Boot, автоматически импортирует любой модуль Jackson, найденный в пути к классу.

Описание этой библиотеки в https://github.com/FasterXML/jackson-modules-java8 говорит:

Имена параметров: поддержка определения параметров конструктора и фабричного метода («создателя») без использования аннотации @JsonProperty. Предоставляет com.fasterxml.jackson.module.paramnames.Параметрнаймодуль

Я все еще не уверен на 100%, почему это создало проблему, которую оно создало, но удаление ее с помощью Maven решило проблему:

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-parameter-names</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  

Ответ №3:

Между Spring Boot 1 и Spring Boot 2 произошли серьезные изменения. Вы уже пробовали что-нибудь исправить? Например. пытались переместить аннотацию JsonProperty в поле, а не в конструктор? или используя @JsonCreator

онлайн-ресурс: https://www.baeldung.com/jackson-deserialize-immutable-objects

Комментарии:

1. Пробовал оба. Точно такое же поведение