Зачем Джексону конструктор по умолчанию?

Я использую Java Spring Boot для своего проекта, и у меня есть следующий контроллер:

@AllArgsConstructor
@RestController
@RequestMapping("/api/subject")
public class SubjectController {
    private SubjectService subjectService;

    @PostMapping
    public void createSubject(@RequestBody SubjectCreationDTO subjectCreationDTO) {
        LoggingController.getLogger().info(subjectCreationDTO.getTitle());
//        subjectService.createSubject(subjectCreationDTO);
    }
}

И SubjectCreationDTO:

@AllArgsConstructor
@Getter
@Setter
public class SubjectCreationDTO {
    private String title;
}

Итак, я получаю эту ошибку при выполнении запроса POST:

Ошибка анализа JSON: невозможно создать экземпляр pweb.examhelper.dto.subject.SubjectCreationDTO (хотя по крайней мере существует один Создатель): невозможно десериализовать из значения объекта (нет Создатель на основе делегата или свойства)"

Я могу решить эту ошибку, добавив @NoArgsConstructor в SubjectCreationDTO, но зачем это нужно, когда в других случаях у меня почти такой же случай.

@PostMapping
public ResponseEntity<StudentDTO> createStudent(@RequestBody StudentCreationDTO studentCreationDTO) {
    StudentDTO savedStudent = studentService.createStudent(studentCreationDTO);
    return new ResponseEntity<>(savedStudent, HttpStatus.CREATED);
}

а это класс StudentCreationDTO:

@AllArgsConstructor
@Getter
@Setter
public class StudentCreationDTO {
    private String username;
    private String firstName;
    private String lastName;
    private String email;
}

Я понял, что в случае наличия более одного поля вам не нужно указывать @NoArgsConstructor, и библиотека Джексона может так же хорошо анализировать входной JSON из тела. Мой вопрос: почему он имеет такое поведение и почему он не может анализировать, если у меня есть только одно поле в классе без конструктора по умолчанию, но может, если у меня несколько полей?

обновлена ​​видимость проекта для публики на сайте oneCompiler

dani-vta 30.03.2024 18:28
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
230
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Чтобы Джексон мог десериализовать Json, ему нужен либо конструктор по умолчанию, либо метод, помеченный @JsonCreator. Без любого из этих двух методов Джексон не может создать экземпляр и вызывает InvalidDefinitionException. Это то, что пытается сказать ошибка, которая не может десериализовать из значения объекта (никакой создатель на основе делегата или свойства).

Используя конструктор по умолчанию, Джексон сначала создает экземпляр класса по умолчанию, а затем внедряет свойства объекта в каждое поле, считанное из Json.

Аналогично, при использовании подхода @JsonCreator Джексон сначала создает экземпляр объекта только со свойствами, указанными в качестве параметров метода, аннотированного @JsonCreator. Затем устанавливает все оставшиеся поля из Json в объект. Аннотированный метод может быть либо параметризованным конструктором, либо статическим методом.

Обычно вы не сможете десериализовать объект с помощью всего лишь @AllArgsContructor, но должна быть какая-то другая конфигурация, которая обрабатывает параметризованное создание экземпляра за вас. Вот также статья из Baeldung, где в пункте 10.1 показан типичный случай, когда класс не десериализуется из-за отсутствия конструктора по умолчанию или метода, помеченного @JsonCreator.

Я также приложил пример, который вы можете попробовать в oneCompiler, где он показывает, как ведет себя Джексон, когда есть только параметризованный конструктор и нет конструктора по умолчанию или @JsonCreator метода. В частности, пример обрабатывает следующие сценарии:

  • StudentCreationDTO1 не десериализуется, поскольку он предоставляет только @AllArgsConstructor и не имеет конструктора по умолчанию. На самом деле выбрасывается InvalidDefinitionException.

  • StudentCreationDTO2 десериализуется, поскольку предоставляет конструктор по умолчанию.

  • StudentCreationDTO3 десериализуется, поскольку предоставляет метод (конструктор), помеченный @JsonCreator. Аннотация не обязательно должна включать все поля класса, а только некоторые из них, просто чтобы Джексон мог создать экземпляр StudentCreationDTO3, а затем установить остальные поля.

public class Main {
    public static void main(String[] args) throws JsonProcessingException {
        String json = "{\n" +
                "\t\"username\": \"johndoe\",\n" +
                "\t\"firstName\": \"john\",\n" +
                "\t\"lastName\": \"doe\",\n" +
                "\t\"email\": \"[email protected]\"\n" +
                "}";
        ObjectMapper objectMapper = new ObjectMapper();

        //Deserializing with no default constructor
        try {
            StudentCreationDTO1 studentCreationDTO1 = objectMapper.readValue(json, StudentCreationDTO1.class);
            System.out.println(studentCreationDTO1);
        } catch (InvalidDefinitionException e) {
            System.out.println("Throwing InvalidDefinitionException because there is no default constructor or method marked with @JsonCreator");
        }

        //Deserializing with default constructor
        try {
            StudentCreationDTO2 studentCreationDTO2 = objectMapper.readValue(json, StudentCreationDTO2.class);
            System.out.println("\n" + studentCreationDTO2);
        } catch (InvalidDefinitionException e) {
            System.out.println("Throwing InvalidDefinitionException because there is no default constructor or method marked with @JsonCreator");
        }

        //Deserializing with no default constructor but with method annotated with @JsonCreator
        try {
            StudentCreationDTO3 studentCreationDTO3 = objectMapper.readValue(json, StudentCreationDTO3.class);
            System.out.println("\n" + studentCreationDTO3);
        } catch (InvalidDefinitionException e) {
            System.out.println("Throwing InvalidDefinitionException because there is no default constructor or method marked with @JsonCreator");
        }
    }
}

Дополнительные примечания по десериализации Джексона

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

  • Если свойство имеет установщик, то его значение устанавливается с помощью соответствующего метода установки.
public class MyBean {
    private String name;

    //... default constructor ....

    //... standard getName() ...

    //Jackson uses the corresponding setter to set the property name
    public void setName(String name) {
        this.name = name;
    }
}
  • Если установщик не указан, но класс содержит метод, помеченный @JsonSetter и именем свойства Json, то Джексон предполагает, что аннотированный метод — это правильный способ установить свойство json.
public class MyBean {
    private String name;

    //... default constructor ....

    //... standard getName() ...

    //Marking the following method with @JsonSetter because
    //The json contains a property called name (value = "name"), 
    //but Jackson can't find any setter method in the form setName()
    @JsonSetter(value = "name")
    public void setTheName(String name) {
        this.name = name;
    }
}
  • Если метод установки или @JsonSetter не указан, но класс содержит только методы получения, Джексон возвращается к отражению, чтобы установить свойства объекта.
public class MyBean {
    private String name;

    //... default constructor ....

    //Jackson cannot set a value with just a getter,
    //so it falls falls back to reflection to set name
    public void getName() {
        return name;
    }
}
  • Если класс не предоставляет никаких методов получения или установки, по умолчанию Джексон устанавливает только общедоступные поля, в противном случае — с любого уровня видимости и выше, когда ObjectMapper настроен с помощью setVisibility() метода.
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

...

public class MyBean {
    //Every field is set by Jackson with Visibility.ANY
    public String name;
    proteced int id;
    float value;
    private boolean flag;

    //... default constructor ....

    //... No getters or setters ....
}

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