I'm using Spring Boot with Thymeleaf and trying to set elements (that are objects) of list using form. More precisely I have 2 entities: Question that aggregates List of Answers , I'm passing Question object to the model and trying to set each Answer object in iteration. When pressing save (submiting form) i got exception:
Field error in object 'question' on field 'answers': rejected value [[Answer(id=0, answerNumber=1, text=, correct=false), Answer(id=0, answerNumber=2, text=, correct=false), Answer(id=0, answerNumber=3, text=, correct=false), Answer(id=0, answerNumber=4, text=, correct=false)]]; codes [typeMismatch.question.answers,typeMismatch.answers,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [question.answers,answers]; arguments []; default message [answers]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'answers'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.easetest.website.model.Answer' for property 'answers[0]': no matching editors or conversion strategy found]]
My Controller class question methods
@PostMapping("/editQuestion")
public String editQuestion(Model model, @RequestParam("test_id") int id, @RequestParam(value = "question_id", required = false) Integer question_id) {
Test test = testService.getById(id);
List<Question> questions = test.getQuestions();
System.out.println(questions);
int realNumberOfQuestion = questions.size(); // mozliwe ze nie potrzebne
test.setMultipleAnswers(false);
Question q = question_id == null ? null : questionService.getById(question_id);
if(q == null) {
q = new Question();
if(realNumberOfQuestion < 1) {
q.setQuestionNumber(1);
} else if(questions.get(realNumberOfQuestion - 1).getQuestionBody().isEmpty()){
q = questions.get(realNumberOfQuestion - 1);
} else {
q.setQuestionNumber(realNumberOfQuestion);
}
}
model.addAttribute("test", test);
model.addAttribute("question", q);
System.out.println(q);
return "business/question_form";
}
@PostMapping("/saveQuestion")
public String saveQuestion(Model model, @ModelAttribute("question") Question question, @RequestParam("test_id") int id) {
System.out.println("Lubu dubu");
Test test = testService.getById(id);
test.setQuestion(question);
testService.save(test);
model.addAttribute("test", test);
model.addAttribute("question", question);
return "business/question_form";
}
Question Form
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<!--Bootstrap dependencies-->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script th:unless="${test.multipleAnswers}">
$(document).on('click', 'input[type="checkbox"]', function () {
$('input[type="checkbox"]').not(this).prop('checked', false);
});
</script>
<meta charset="UTF-8">
<title>Create Question</title>
</head>
<body class="mb-2 bg-secondary text-white">
<div class="container">
<div>
<h2 th:text="${test.testName}"></h2>
</div>
<div class="form-group">
<h4>Add question</h4>
<form action="#" th:action="@{/business/saveQuestion}" th:object="${question}" method="post">
<div class="form-group">
<input type="hidden" th:field="*{id}">
<input type="hidden" th:field="*{answers}">
<input type="hidden" th:name="test_id" th:value="${test.id}">
<div class="form-group">
<label for="exampleFormControlTextarea1">Question body</label>
<textarea class="form-control" id="exampleFormControlTextarea1"
th:value="${question.questionBody}" rows="3"></textarea>
</div>
<br>
<div th:each="answer, itemStat : *{answers}">
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="hidden" th:field="*{answers[__${itemStat.index}__].id}">
<input type="hidden" th:field="*{answers[__${itemStat.index}__].answerNumber}">
<input type="checkbox" th:field="*{answers[__${itemStat.index}__].correct}">
</div>
</div>
<input type="text" class="form-control" th:field="*{answers[__${itemStat.index}__].text}">
</div>
</div>
</div>
<br>
<input type="submit" th:value="Save" class="btn btn-info">
</form>
</div>
<ul class="pagination">
<th:block th:each="question: ${test.questions}">
<form method="post" action="#" th:action="@{/business/editQuestion}">
<input type="hidden" th:name="test_id" th:value="${test.id}">
<input type="hidden" th:name="question_id" th:value="${question.id}">
<input type="submit" class="btn btn-danger" th:value=${question.questionNumber}>
</form>
</th:block>
<form method="post" action="#" th:action="@{/business/addQuestion}">
<input type="hidden" th:name="test_id" th:value="${test.id}">
<input type="hidden" th:name="question_id" th:value="${question.id}">
<input type="submit" class="btn btn-success" value="New Question">
</form>
</ul>
</div>
</body>
</html>
Question entity
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Question {
@Id
@GeneratedValue(strategy= GenerationType.AUTO, generator="native")
@GenericGenerator(name = "native", strategy = "native")
private int id;
@EqualsAndHashCode.Exclude
private int questionNumber;
@EqualsAndHashCode.Exclude
private String questionBody = "";
@EqualsAndHashCode.Exclude
@ToString.Exclude
@ManyToOne
@JoinColumn(name = "test_id", nullable = false)
private Test test;
@EqualsAndHashCode.Exclude
@OneToMany(cascade = CascadeType.ALL, mappedBy = "question", orphanRemoval = true)
private List<Answer> answers = Arrays.asList(
new Answer(1, "", false),
new Answer(2, "", false),
new Answer(3, "", false),
new Answer(4, "", false));
public Question(int questionNumber) {
this.questionNumber = questionNumber;
}
public void setAnswer(Integer num, String answer, boolean correct) {
System.out.println(answers);
if(answers == null) {
answers = new ArrayList<>();
}
Answer a = answers.get(num - 1) != null ? answers.get(num - 1) : new Answer();
a.setAnswerNumber(num);
a.setText(answer);
a.setCorrect(correct);
a.setQuestion(this);
answers.set(num-1, a);
}
public void removeAnswer(Integer num) {
if(answers == null) {
return;
}
answers.set(num-1, new Answer(num, "", false));
}
}
Answer entity
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Answer {
@Id
@GeneratedValue(strategy= GenerationType.AUTO, generator="native")
@GenericGenerator(name = "native", strategy = "native")
private int id;
@EqualsAndHashCode.Exclude
private int answerNumber;
@EqualsAndHashCode.Exclude
private String text;
@EqualsAndHashCode.Exclude
private boolean correct;
@EqualsAndHashCode.Exclude
@ToString.Exclude
@ManyToOne
@JoinColumn(name = "question_id")
private Question question;`enter code here`
public Answer(int answerNumber, String text, boolean correct) {
this.answerNumber = answerNumber;
this.text = text;
this.correct = correct;
}
}
Does anyone know what cause that error and how can I solve it?
The most important message of the error message is: Cannot convert value of type 'java.lang.String' to required type 'com.easetest.website.model.Answer' . To solve the problem I had to:
create converter that implements Converter<String, Answer>
add that converter in WebConfig
implementation below:
Converter class:
import com.easetest.website.model.Answer;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class StringToAnswerConverter implements Converter<String, Answer> {
@Override
public Answer convert(String s) {
String[] data = s.split(",");
return new Answer(
Integer.parseInt(getSubstringAfterChar(data[1], '=')),
new String(getSubstringAfterChar(data[2], '=')),
Boolean.parseBoolean(getSubstringAfterChar(data[3], '=')));
}
private String getSubstringAfterChar(String text, char character) {
return text.substring(text.lastIndexOf(character) + 1);
}
}
WebConfig class
import com.easetest.website.converters.StringToAnswerConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToAnswerConverter());
}
}
Hope it will be usefull to somebody.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.