Extra field coming in get response in spring boot application - spring

In my spring boot application I have booking controller which has corresponding service,repository and controller.
My booking Model looks like this :
#JsonFormat(pattern = "yyyy-MM-dd")
#Column(name = "datetime")// corresponds to value
private Date date;
public Date getDatetime() {
return this.date;
}
public void setDateTime(Date dateTime) {
this.date = dateTime;
}
Controller
#GetMapping("api/booking_details/{userEmail}")
#ResponseBody
public ResponseEntity<List<Booking>> getDetails(
#PathVariable #Email String userEmail) {
return new ResponseEntity<>(bookService.findByEmail(userEmail), HttpStatus.OK);
}
My corresponding get request is
api/booking_details/
the response I am getting is :
{
"datetime": "2021-09-12T16:01:04.000+00:00",
"date": "2021-09-12"
}
Can any one let me know what could be reason for having two values in response?

The problem is getter and setter method are not identified as date parameter's getter and setter methods. Two things you can do,
Rename getter and setter method name as follows,
public Date getDate() {
return this.date;
}
public void setDate(Date date) {
this.date = date;
}
Or annotate getter and setter method using #JsonProperty("date") annotation. Added this annotation for only getter method is also sufficient.
#JsonProperty("date")
public Date getDatetime() {
return this.date;
}
#JsonProperty("date")
public void setDateTime(Date dateTime) {
this.date = dateTime;
}

It looks like both the "date" property and the return value of the getDateTime() method are being serialized.
I wouldn't normally expect the "date" property, which is private, to get serialzied, but perhaps it is because you have the #JsonFormat annotation on it?
The getDateTIme() return value is serialized because it's name indicates that it's a Java Bean property.
If what you are looking for is just the formatted date, I'd try moving the #JsonFormat annotation to the method.

Related

Unable to convert pathvariable to object

I want to have an object as a path variable but I get the below exception when testing. How can I fix this
#Validated
#RestController
#RequestMapping("/test/api")
public class MyRestController {
#GetMapping("/data/{id}")
public Data getData(#PathVariable #Valid IdData id) {
return new Data();
}
}
#Data
public class IdData {
private Integer id;
public IdData(Integer id) {
this.id = id;
}
}
Exception:
org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException:
Failed to convert value of type 'java.lang.String' to required type
'com.test.IdData'; nested exception is
java.lang.IllegalStateException: Cannot convert value of type
'java.lang.String' to required type 'com.test.IdData': no matching
editors or conversion strategy found
From "/data/{id}" you will get an id which is an integer but the method parameter is trying to get IdData value which is incompatible.
#Validated
#RestController
#RequestMapping("/test/api")
public class MyRestController {
#GetMapping("/data/{id}")
public Data getData(#Valid #PathVariable int id) {
return new Data();
}
}
Output:-
{
"repository": {
"metricName": "spring.data.repository.invocations",
"autotime": {
"enabled": true,
"percentilesHistogram": false,
"percentiles": null
}
}
}
you could change your #PathVariable Data Type from IdData to an Integer. Just add some logic to get the IdData by the id in path, which can be done by using JPA's findById() method. It might also be easier to pass in an integer in the path rather than an entire object.

Failed to convert value of type 'java.lang.String' to required type 'java.util.Date' on swagger

I want to reach a function in the database with the spring boot api and get the value it returns.
When we enter the parameters in swagger, it gives an error in the date part.
When I call the date parameters to the function in oracle as 01-apr-2021, there is no error, but I cannot send it this way from spring.
Oracle funtion code :
CREATE OR REPLACE PACKAGE BODY MET.Z_PKG_OEE_NEW
FUNCTION Z_OEE_A1AfterReworkRatio(V_plant_config_num_id IN number, p_start_date in date, p_stop_date in date) RETURN NUMBER IS
v_result NUMBER;
p_cur001 SYS_REFCURSOR;
BEGIN
Z_OEE_A1AfterReworkRatio_Detail(V_plant_config_num_id,p_start_date,p_stop_date,p_cur001, v_result);
RETURN round(v_result,4);
END Z_OEE_A1AfterReworkRatio;
end;
ooeController:
#RestController
#RequestMapping("/api/oeeReports")
#CrossOrigin
public class OeeController {
private OeeReportService oeeReportService;
#Autowired
public OeeController(OeeReportService oeeReportService) {
this.oeeReportService=oeeReportService;
}
#GetMapping("A1AfterReworkRatio")
BigDecimal A1AfterReworkRatio(#RequestParam int V_plant_config_num_id, #RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date p_start_date ,#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date p_stop_date) {
return this.oeeReportService.A1AfterReworkRatio( V_plant_config_num_id , p_start_date, p_stop_date);
}
}
oeeservice:
#Service
public class OeeReportManager implements OeeReportService {
private OeeDao oeeDao;
#Autowired
public OeeReportManager(OeeDao oeeDao) {
super();
this.oeeDao=oeeDao;
}
#Override
public BigDecimal A1AfterReworkRatio(int V_plant_config_num_id, Date p_start_date, Date p_stop_date) {
// TODO Auto-generated method stub
return this.oeeDao.A1AfterReworkRatio(V_plant_config_num_id, p_start_date, p_stop_date);
}
}
oeedao :
#Repository
public class OeeDao {
#Autowired
private EntityManager entitymanager;
public BigDecimal A1AfterReworkRatio(int V_plant_config_num_id,Date p_start_date,Date p_stop_date) {
BigDecimal commentCount = (BigDecimal) entitymanager
.createNativeQuery(
"SELECT Z_OEE_A1AfterReworkRatio(:V_plant_config_num_id:p_start_date:p_stop_date) FROM DUAL"
)
.setParameter("V_plant_config_num_id", V_plant_config_num_id).setParameter("p_start_date", p_start_date).setParameter("p_stop_date", p_stop_date)
.getSingleResult();
return commentCount;
}
}
swagger :
error :
{
"timestamp": "2021-08-26T07:00:23.487+00:00",
"status": 500,
"error": "Internal Server Error",
"trace": "org.springframework.dao.InvalidDataAccessApiUsageException: Could not locate named parameter [V_plant_config_num_id], expecting one of [V_plant_config_num_id:p_start_date:p_stop_date]; nested exception is java.lang.IllegalArgumentException: Could not locate named parameter [V_plant_config_num_id], expecting one of [V_plant_config_num_id:p_start_date:p_stop_date]\r\n\tat
How can solve this problem?
According to https://www.baeldung.com/spring-date-parameters
you can annotate your date parameters in OeeController (from: spring boot application {while creating beans error}) with #DateTimeFormat(iso = DateTimeFormat.ISO.DATE):
#GetMapping("A1AfterReworkRatio")
int A1AfterReworkRatio(#RequestParam int V_plant_config_num_id,
#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date p_start_date,
#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date p_stop_date) {
return this.oeeReportService.A1AfterReworkRatio( V_plant_config_num_id , p_start_date, p_stop_date);
}
The above article describes also other methods of achieving it if you read through it.

Is there a way to configure LocalDate format for serializing and deserializing in the whole spring application?

I have the following problem I hope someone can give me a hand:
Context: 3 Rest endpoints
Create (register)
Find (findKid)
Report (listDashboardInfo)
Requirement: Use the same date format yyyyMMdd for LocalDates in the whole application
Problem: Using #DateTimeFormat(pattern = DateUtils.SHORT_DATE_PATTERN) works for register and listDashboardInfo but not for findKid
These are the relevant parts of the code:
BODY
{
"sailDate": "20191201"
}
#PostMapping(KID_PATH)
#ResponseStatus(HttpStatus.CREATED)
public KidDTO register(#RequestBody #Valid KidDTO kid) {
return kidService.saveKid(kid);
}
GET /kid/0001::20190901
RESPONSE
{
"sailDate": "2019-09-01"
}
#GetMapping(KID_FIND_PATH)
public CompletableFuture<KidDTO> findKid(#PathVariable String id) {
return kidService.findKid(id);
}
GET /kid?shipCode=AL&sailDate=20190901
#GetMapping(KID_LIST_PATH)
public CompletableFuture<Slice<DashboardDTO>> listDashboardInfo(#Valid DashboardFilter filter, Pageable pageable) {
return kidService.listKidsWithStatistics(filter, pageable);
}
#Getter
#Setter
public class DashboardFilter {
#NotNull
#DateTimeFormat(pattern = DateUtils.SHORT_DATE_PATTERN)
private LocalDate sailDate;
}
#Data
public class KidDTO {
#NotNull
#DateTimeFormat(pattern = DateUtils.SHORT_DATE_PATTERN)
private LocalDate sailDate;
}
Tests I did:
spring.jackson.date-format in application.properties: From https://blog.codecentric.de/en/2017/08/parsing-of-localdate-query-parameters-in-spring-boot/ this just apply for Date not LocalDate.
Using #JsonFormat(pattern = DateUtils.SHORT_DATE_PATTERN) the listDashboardInfo doesn't recognize the format and generates error
From stackoverflow I also found Spring doesn't use Jackson to deserialize query params so:
- I created a #ControllerAdvice with #InitBinder but the method setAsText is never called:
#ControllerAdvice
public class GlobalDateBinder {
#InitBinder
public void binder(WebDataBinder binder) {
binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
#Override
public void setAsText(String text) throws IllegalArgumentException {
LocalDate.parse(text, DateUtils.SHORT_DATE_FORMATTER);
}
});
}
}
Also I tried with a #Bean public Formatter<LocalDate> localDateFormatter() but nothing change:
#Bean
public FormattingConversionService conversionService() {
DefaultFormattingConversionService conversionService =
new DefaultFormattingConversionService(false);
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateFormatter(DateUtils.SHORT_DATE_FORMATTER);
registrar.registerFormatters(conversionService);
return conversionService;
}
#Bean
public Formatter<LocalDate> localDateFormatter() {
return new Formatter<LocalDate>() {
#Override
public LocalDate parse(String text, Locale locale) {
return LocalDate.parse(text, DateUtils.SHORT_DATE_FORMATTER);
}
#Override
public String print(LocalDate object, Locale locale) {
return DateUtils.SHORT_DATE_FORMATTER.format(object);
}
};
}
Any one has an idea of what is happening?
how to make the response of findKid be formatted?
How to configure the whole application with the same date format to works in serialization and parsing/deserializing processes?
UPDATE:
I found here https://stackoverflow.com/questions/30871255/spring-boot-localdate-field-serialization-and-deserialization that I can use #JsonFormat for rest controllers (serialize and deserialize) and #DateTimeFormat for ModelView controllers but using both, at the same time, fixed my error so I don't understand why is that behavior if I only have rest controllers. Looks like in my case #DateTimeFormat deserialize and #JsonFormat serialize, is that the expected behavior? Is there any misconfiguration?
you can add this bean to you configuration:
#Bean
public ObjectMapper objectMapper() {
DateTimeFormatter dateFormatter; // create your date formatter
DateTimeFormatter dateTimeFormatter; // create your date and time formatter
ObjectMapper mapper = new ObjectMapper();
SimpleModule localDateModule = new SimpleModule();
localDateModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(formatter));
localDateModule.addSerializer(LocalDate.class,
new LocalDateSerializer(formatter));
localDateModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(dateTimeFormatter));
localDateModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(dateTimeFormatter));
mapper.registerModules(localDateModule);
return mapper;
}
Just set the property spring.jackson.date-format to any format you want inside you application.properties or application.yml.
Example with application.properties:
spring.jackson.date-format=yyyyMMdd
Example with application.yml:
spring:
jackson:
date-format: yyyyMMdd
Source and other available properties: https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html

Spring Data JPA JpaRepository only uses No Arg Constructor

I have this simple REST API that i created with Spring Boot.
In this app, I have a a POJO called Expense with 4 fields. I have a no Argument constructor and another constructor that takes only two inputs. One String value "item" and one Integer value "amount". The date is set using the LocalData.now() method and the id is set automatically in a MySql db running in the server.
Here's my Entity class
#Entity
public class Expense {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Integer id;
private String date;
private String item;
private Integer amount;
//No Arg Construction required by JPA
public Expense() {
}
public Expense(String item, Integer amount) {
this.date = LocalDate.now().toString();
this.item = item;
this.amount = amount;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
}
I have another class with RestController annotation where i have set a method to post Expense object with a post method using Request Mapping annotation.
#RestController
public class ExpController {
private ExpService expService;
private ExpenseRepo expenseRepo;
#Autowired
public ExpController(ExpService expService, ExpenseRepo expenseRepo) {
this.expService = expService;
this.expenseRepo = expenseRepo;
}
#RequestMapping(path = "/addExp", method=RequestMethod.POST)
public void addExp(Expense expense){
expenseRepo.save(expense);
}
}
Now finally i am using PostMan to make the HTTP Post Request. I have made a simple Json Format text to send Item and Amount
{
"item":"Bread",
"amount": 75
}
After I make the post request, all i can see is that a new Entry is created but all values are set to null.
I have done some experimentation and found out that the expenseRepo.save(expense) method is only using the default no Arg constructor to save the data. But it's not using the second constructor that takes the two parameters that I am passing through Postman
How to solve this issue. Please help
Change your controller method like this
#RequestMapping(path = "/addExp", method=RequestMethod.POST)
public void addExp(#RequestBody Expense expense){
expenseRepo.save(expense);
}
You need to use #RequestBody

Validate input before Jackson in Spring Boot

I've built a REST endpoint using Spring Boot. JSON is posted to the endpoint. Jackson converts the JSON giving me an object.
The JSON look like this:
{
"parameterDateUnadjusted": "2017-01-01",
"parameterDateAdjusted": "2017-01-02"
}
Jackson converts the JSON to an object based on this class:
public class ParameterDate {
#NotNull(message = "Parameter Date Unadjusted can not be blank or null")
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date parameterDateUnadjusted;
#NotNull(message = "Parameter Date Adjusted can not be blank or null")
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date parameterDateAdjusted;
private Date parameterDateAdded;
private Date parameterDateChanged;
}
This all works fine. The issue I'm having is that I would like to validate the data before Jackson converts the data. For instance if I post
{
"parameterDateUnadjusted": "2017-01-01",
"parameterDateAdjusted": "2017-01-40"
}
Where parameterDateAdjusted is not a valid date (there is no month with 40 days in it). Jackson converts this to 2017-02-09. One way of getting around this is to have a class that is only strings let's call it ParameterDateInput. Validate each filed with Hibernate Validator in the parameterDateInput object and then copy the parameterDateInput object to parameterDate where each field has the correct type (dates are of type Date and not of type String). This to me doesn't look like a very elegant solution. Is there some other way I can solve this? How is data generally validated in Spring Boot when posted as JSON? I like to be able to send back a message to the user/client what is wrong with the data that is being posted.
How about a custom JSON deserializer where you can write down the logic you want:
#RestController
public class JacksonCustomDesRestEndpoint {
#RequestMapping(value = "/yourEndPoint", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Object createRole(#RequestBody ParameterDate paramDate) {
return paramDate;
}
}
#JsonDeserialize(using = RoleDeserializer.class)
public class ParameterDate {
// ......
}
public class RoleDeserializer extends JsonDeserializer<ParameterDate> {
#Override
public ParameterDate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
String parameterDateUnadjusted = node.get("parameterDateUnadjusted").getTextValue();
//Do what you want with the date and set it to object from type ParameterDate and return the object at the end.
//Don't forget to fill all the properties to this object because you do not want to lose data that came from the request.
return something;
}
}
There is a way to check the dates. setLenient() method
public static boolean isValidDate(String inDate, String format) {
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setLenient(false);
try {
dateFormat.parse(inDate.trim());
} catch (ParseException pe) {
return false;
}
return true;
}
Just define own annotation to validate the value
#Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = MyDateFormatCheckValidator.class)
#Documented
public #interface MyDateFormatCheck {
String pattern();
...
and the validator class
public class MyDateFormatCheckValidator implements ConstraintValidator<MyDateFormatCheck, String> {
private MyDateFormatCheck check;
#Override
public void initialize(MyDateFormatCheck constraintAnnotation) {
this.check= constraintAnnotation;
}
#Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if ( object == null ) {
return true;
}
return isValidDate(object, check.pattern());
}
}

Resources