Issue
I am having a problem converting input String to money. I am creating a spring boot application with thymeleaf. I have a web page where user inputs the data and there is a particular field where he inputs the String and it needs to be converted to type joda.money as in my pojo class that field has money data type. This is a full governor_form.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>[[${pageTitleG}]]</title>
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}"/>
</head>
<body>
<div class="container-fluid">
<div class="text-center"><h2>[[${pageTitleG}]]</h2></div>
<form th:action="@{/governors/save}" method="post" th:object="${governor}" style="max-width: 550px; margin: 0 auto;">
<input type="hidden" th:field="*{idGovernor}">
<div class="border border-secondary rounded p-3">
<div class="form-group row">
<label class="col-sm-4 col-form-label">Full name:</label>
<div class="col-sm-8">
<input type="text" th:field="*{fullName}" class="form-control" required minlength="5" maxlength="70">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Age</label>
<div class="col-sm-8">
<input type="number" step="0.01" th:field="*{age}" class="form-control" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Position</label>
<div class="col-sm-8">
<input type="text" step="0.01" th:field="*{position}" class="form-control" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Date of Intercession</label>
<div class="col-sm-8">
<input type="date" th:field="*{dateOfIntercession}" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Date of Resignation</label>
<div class="col-sm-8">
<input type="date" th:field="*{dateOfResignation}" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Per Capita Income</label>
<div class="col-sm-8">
<input type="number" step="0.01" th:field="*{perCapitaIncome}" class="form-control" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Population Below Poverty</label>
<div class="col-sm-8">
<input type="number" step="0.01" th:field="*{populationBelowPoverty}" class="form-control" required min="0" max="99">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">State</label>
<div class="col-sm-8">
<select th:field="*{state}" class="form-control" required>
<th:block th:each="state : ${listStates}">
<option th:text="${state.officialStateName}" th:value="${state.idState}"/>
</th:block>
</select>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary m-2">Save</button>
<button type="button" class="btn btn-secondary m-2" onclick="cancelForm()">Cancel</button>
</div>
</div>
</form>
</div>
<script type="text/javascript">
function cancelForm() {
window.location = "[[@{/governors}]]";
}
</script>
</body>
</html>
This is where he inputs that data:
<div class="form-group row">
<label class="col-sm-4 col-form-label">Per Capita Income</label>
<div class="col-sm-8">
<input type="number" step="0.01" th:field="*{perCapitaIncome}" class="form-control" required>
</div>
</div>
So if i'm not mistaken Hibernate is having a problem converting a String input from thymeleaf to type money as I get the following error:
: Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors<EOL>Field error in object 'governor' on field 'perCapitaIncome': rejected value [1100]; codes [typeMismatch.governor.perCapitaIncome,typeMismatch.perCapitaIncome,typeMismatch.org.joda.money.Money,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [governor.perCapitaIncome,perCapitaIncome]; arguments []; default message [perCapitaIncome]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.joda.money.Money' for property 'perCapitaIncome'; Cannot convert value of type 'java.lang.String' to required type 'org.joda.money.Money' for property 'perCapitaIncome': no matching editors or conversion strategy found]]
This is the joda.money
dependency:
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
This is a POJO class:
@Entity
@Table(name = "governor")
public class Governor implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id_governor")
private Integer idGovernor;
@Column(name = "pib")
private String fullName;
@Column(name = "age")
private Integer age;
@Column(name = "position")
private String position;
@Column(name = "date_of_intercession")
private java.sql.Date dateOfIntercession;
@Column(name = "date_of_resignation")
private java.sql.Date dateOfResignation;
@Column(name = "per_capita_income")
private Money perCapitaIncome;
@Column(name = "population_below_poverty")
private Integer populationBelowPoverty;
@ManyToOne
@JoinColumn(name = "id_state", nullable = false)
private State state;
public State getState() {return state;}
public void setState(State state) {this.state = state;}
public Integer getIdGovernor() {
return this.idGovernor;
}
public void setIdGovernor(Integer idGovernor) {
this.idGovernor = idGovernor;
}
public String getFullName() {
return this.fullName;
}
public void setFullName(String fullName) {this.fullName = fullName;}
public Integer getAge() {
return this.age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPosition() {
return this.position;
}
public void setPosition(String position) {
this.position = position;
}
public java.sql.Date getDateOfIntercession() {
return this.dateOfIntercession;
}
public void setDateOfIntercession(java.sql.Date dateOfIntercession) {
this.dateOfIntercession = dateOfIntercession;
}
public java.sql.Date getDateOfResignation() {
return this.dateOfResignation;
}
public void setDateOfResignation(java.sql.Date dateOfResignation) {
this.dateOfResignation = dateOfResignation;
}
public Money getPerCapitaIncome() {
return this.perCapitaIncome;
}
public void setPerCapitaIncome(Money perCapitaIncome) {
this.perCapitaIncome = perCapitaIncome;
}
public Integer getPopulationBelowPoverty() {
return this.populationBelowPoverty;
}
public void setPopulationBelowPoverty(Integer populationBelowPoverty) {
this.populationBelowPoverty = populationBelowPoverty;
}
}
This is a controller:
@Controller
public class GovernorController {
@Autowired
GovernorService governorService;
@Autowired
GovernorRepository governorRepository;
@Autowired
StateRepository stateRepository;
@Autowired
StateService stateService;
@GetMapping("/governors")
public String showAllGovernors(Model model){
List<Governor> listGovernors = governorService.findAllGovernors();
model.addAttribute("listGovernors", listGovernors);
return "governors";
}
@GetMapping("/governors/new")
public String showNewGovernorForm(Model model){
List <State> listStates = stateService.findAll();
model.addAttribute("listStates", listStates);
model.addAttribute("governor", new Governor());
model.addAttribute("pageTitleG", "Add New Governor");
return "governor_form";
}
@PostMapping("/governors/save")
public String saveGovernor (Governor requestGovernor, RedirectAttributes redirectAttributes){
governorRepository.save(requestGovernor);
redirectAttributes.addFlashAttribute("messageG", "The governor has been saved successfully!");
return "redirect:/governors";
}
@GetMapping("/governors/edit/{id}")
public String showEditGovernorForm(@PathVariable("id") Integer id, Model model, RedirectAttributes redirectAttributes){
try {
Governor governor = governorService.findGovernorById(id);
List <State> listStates = stateService.findAll();
model.addAttribute("listStates", listStates);
model.addAttribute("governor", governor);
model.addAttribute("pageTitleG", "Edit Governor (ID: " + id + ")");
return "governor_form";
} catch (EntityNotFoundException e) {
redirectAttributes.addFlashAttribute("messageG", e.getMessage());
return "redirect:/governors";
}
}
@GetMapping("/governors/delete/{id}")
public String deleteGovernor(@PathVariable("id") Integer id, Model model, RedirectAttributes redirectAttributes){
try {
governorService.deleteGovernor(id);
redirectAttributes.addFlashAttribute("messageG", "The governor ID " + id + " has been deleted!");
} catch (StateNotFoundException e) {
redirectAttributes.addFlashAttribute("messageG", e.getMessage());
}
return "redirect:/governors";
}
}
How do I convert String to Money?
Solution
One solution would be to create custom deserilizer for Money field, and then use Jackson to set it.
Here is code snippet:
MoneyDeserializer.java :
public class MoneyDeserializer extends StdDeserializer<BigMoney> {
private static final long serialVersionUID = 2518470236548239933L;
public MoneyDeserializer() {
super(Money.class);
}
@Override
public BigMoney deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
return BigMoney.of(CurrencyUnit.USD, jp.readValueAs(Double.class));
}
}
Governor.java
import com.example.demo.filter.MoneyDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.joda.money.BigMoney;
import org.joda.money.Money;
import java.io.Serializable;
public class Governor implements Serializable {
private Integer idGovernor;
private String fullName;
private Integer age;
private String position;
private java.sql.Date dateOfIntercession;
private java.sql.Date dateOfResignation;
private BigMoney perCapitaIncome;
private Integer populationBelowPoverty;
public Integer getIdGovernor() {
return this.idGovernor;
}
public void setIdGovernor(Integer idGovernor) {
this.idGovernor = idGovernor;
}
public String getFullName() {
return this.fullName;
}
public void setFullName(String fullName) {this.fullName = fullName;}
public Integer getAge() {
return this.age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPosition() {
return this.position;
}
public void setPosition(String position) {
this.position = position;
}
public java.sql.Date getDateOfIntercession() {
return this.dateOfIntercession;
}
public void setDateOfIntercession(java.sql.Date dateOfIntercession) {
this.dateOfIntercession = dateOfIntercession;
}
public java.sql.Date getDateOfResignation() {
return this.dateOfResignation;
}
public void setDateOfResignation(java.sql.Date dateOfResignation) {
this.dateOfResignation = dateOfResignation;
}
public BigMoney getPerCapitaIncome() {
return this.perCapitaIncome;
}
@JsonDeserialize(using = MoneyDeserializer.class)
public void setPerCapitaIncome(BigMoney perCapitaIncome) {
this.perCapitaIncome = perCapitaIncome;
}
public Integer getPopulationBelowPoverty() {
return this.populationBelowPoverty;
}
public void setPopulationBelowPoverty(Integer populationBelowPoverty) {
this.populationBelowPoverty = populationBelowPoverty;
}
@Override
public String toString() {
return "Governor{" +
"idGovernor=" + idGovernor +
", fullName='" + fullName + '\'' +
", age=" + age +
", position='" + position + '\'' +
", dateOfIntercession=" + dateOfIntercession +
", dateOfResignation=" + dateOfResignation +
", perCapitaIncome=" + perCapitaIncome +
", populationBelowPoverty=" + populationBelowPoverty +
'}';
}
}
NOTE: On your form, you are passing age
field as Double, but in your class it's declared as Integer
. So you will get exception during deserilization process.
Same thing applys for populationBelowPoverty
field . Also, your date format is dd/MM/YYYY
and I think this is not supported format for jackson. It should be YYYY-MM-dd
.
Anyway, for example, if you send a JSON like this:
{
"idGovernor":1,
"fullName":"Test",
"age":1,
"dateOfIntercession":"2022-06-09",
"dateOfResignation":"2022-06-17",
"perCapitaIncome":"123.932",
"position":"position",
"populationBelowPoverty":"98"
}
to this controller method:
@PostMapping(value = "/test/governor")
public Governor testGovernor(@RequestBody Governor governor) {
return governor;
}
You should get response like this :
{
"idGovernor": 1,
"fullName": "Test",
"age": 1,
"position": "position",
"dateOfIntercession": "2022-06-09",
"dateOfResignation": "2022-06-17",
"perCapitaIncome": {
"amount": 123.932,
"zero": false,
"negative": false,
"positive": true,
"amountMajorLong": 123,
"negativeOrZero": false,
"amountMinorLong": 12393,
"amountMinor": 12393,
"positiveOrZero": true,
"minorPart": 93,
"scale": 3,
"amountMinorInt": 12393,
"currencyUnit": {
"code": "USD",
"numericCode": 840,
"decimalPlaces": 2,
"symbol": "$",
"numeric3Code": "840",
"countryCodes": [
"PR",
"MP",
"IO",
"FM",
"PW",
"GU",
"BQ",
"TC",
"VG",
"AS",
"VI",
"TL",
"UM",
"MH",
"EC",
"US"
],
"pseudoCurrency": false
},
"currencyScale": false,
"amountMajorInt": 123,
"amountMajor": 123
},
"populationBelowPoverty": 98
}
In your case, since you are using thymeleaf you can adjust this idea to your needs.
Answered By - Nemanja
Answer Checked By - Katrina (JavaFixing Volunteer)