Unable to convert pathvariable to object - spring-boot

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.

Related

Extra field coming in get response in spring boot application

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.

Deserializing interfaces with Spring Data MongoDB

i've currently a problem with the serialize/deserialize in MongoDB of an object that contains an attribute defined by a marker interface. The implementations are Enum.
My versions are Spring 4.3.7 and Spring-data-mongodb 1.10.1.
My code sounds like:
public interface EventType {
String getName();
}
public interface DomainEvent extends Serializable {
UUID getId();
LocalDateTime getOccurredOn();
EventType getEventType();
String getEventName();
}
public abstract class AbstractDomainEvent implements DomainEvent {
private UUID id;
private LocalDateTime occurredOn;
private EventType eventType;
protected AbstractDomainEvent(EventType eventType) {
this.id = UUID.randomUUID();
this.occurredOn = LocalDateTime.now();
this.eventType = eventType;
}
}
public class MyEventOne extends AbstractDomainEvent {
private Object myConcreteData;
public MyEventOne(Object data) {
super(MyEventType.EVENT_ONE);
this.myConcreteData = data;
}
}
public enum MyEventType implements EventType {
EVENT_ONE,
EVENT_N;
#Override
public String getName() {
return this.name();
}
}
Ok, well.
My problem is when I try to deserialize an event persisted in mongoDB.
When I persist MyEventOne, Spring data mongo persist the object as:
{
"_class" : "xxx.xxx.xxx.MyEventOne",
"_id" : LUUID("d74478e7-258c-52c4-4fc5-aba20a30d4b6"),
"occurredOn" : ISODate("2018-02-21T14:39:53.549Z"),
"eventType" : "EVENT_ONE"
}
}
Note the eventType is a String.
When I try to read this document, I have this exception:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [xxx.xxx.xxx.EventType]
Any idea? there a some solution like a insert metadata information about the concrete Enum instance, like a "_class" field?
I try insert #JsonTypeInfo annotation in EventType attribute at AbstractDomainEvent but it doesnt works.
Thank you!!!

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());
}
}

Spring #Requestbody not mapping to inner class

I am doing Spring Rest Api project with Spring 4.x
This Works:
Controller.java
#PostMapping("newTransaction")
TransactionRequestModel insertNewTransaction(#RequestBody TransactionRequestModel model){
//do something
}
TransactionRequestModel.java
public class TransactionRequestModel {
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}
KeyValue.java
public class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
Request Body Json
{
"id": 1
"keyValueList": [
{
"key": "dummy",
"value": "dummy"
}
]
}
Spring message converter using jackson is working fine.
This Won't:
When i change TransactionRequestModel.java to following (and delete KeyValue.java)
public class TransactionRequestModel {
public class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}
means, making KeyValue an inner class, got following error.
org.springframework.http.converter.HttpMessageNotReadableException:
Could not read document: No suitable constructor found for type
[simple type, class
com.example.model.TransactionRequestModel$KeyValue]: can not
instantiate from JSON object (missing default constructor or creator,
or perhaps need to add/enable type information?)
Why?
All the related post in SO mentions the first scenario. I would like to know why this wont work. Please help.
You have to make your inner class static.
public class TransactionRequestModel {
public static class KeyValue {
String key;
String value;
//default constructor
//setter-getter
}
private int id;
private List<KeyValue> keyValueList;
public TransactionRequestModel(){}
//default constructor
//getter-setter
}

Unable to fix: 'java.lang.String' to required type 'java.util.Collection'

I'm getting this error when I submit my form and cannot figure out why this is happening. I believe the taglib should be handling this. I've tried changing the value passed in my jsp to itemValue="id" but it has no affect.
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'content' on field 'stateCollection': rejected value [com.myapp.cmt.model.State[ id=3 ]]; codes [typeMismatch.content.stateCollection,typeMismatch.stateCollection,typeMismatch.java.util.Collection,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [content.stateCollection,stateCollection]; arguments []; default message [stateCollection]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Collection' for property 'stateCollection'; nested exception is java.lang.IllegalStateException:
Cannot convert value of type [java.lang.String] to required type [com.myapp.cmt.model.State] for property 'stateCollection[0]': no matching editors or conversion strategy found]
My jsp
<strong>State</strong><br/>
<form:checkboxes path="stateCollection" items="${states}" itemLabel="name"/>
My Content
public class Content implements Serializable {
.......
#JoinTable(name = "content_to_state", joinColumns = {
#JoinColumn(name = "content_id", referencedColumnName = "id")}, inverseJoinColumns = {
#JoinColumn(name = "state_id", referencedColumnName = "id")})
#ManyToMany
private Collection<State> stateCollection;
.....
#XmlTransient
public Collection<State> getStateCollection() {
return stateCollection;
}
public void setStateCollection(Collection<State> stateCollection) {
this.stateCollection = stateCollection;
}
.....
My Controller
...
#RequestMapping(value = "/{guid}/save", method = RequestMethod.POST)
public ModelAndView saveContent(#ModelAttribute("content") Content content, #PathVariable("guid") String guid) {
try {
// Save the modified object
contentService.save(content);
} catch (IllegalOrphanException ex) {
...
My content service
...
#Transactional
public void save(Content content) throws IllegalOrphanException, NonexistentEntityException, RollbackFailureException, Exception {
try {
utx.begin();
em.merge(content);
utx.commit();
} catch (Exception ex) {
} finally {
if (em != null) {
em.close();
}
}
}
...
Your title isn't correct. You have declared a Collection<State> your input is a String. Spring couldn't know how to make a State from a String, you have to tell it. Please see this question: Converting from String to custom Object for Spring MVC form Data binding?
I had the same problem. i'm using Spring, Hibernate.
I have one class with composite primary key and pass two parameters in request, my mistake was:
#Entity
#Table(name = "TAREAS")
public class Tarea implements Serializable {
private static final long serialVersionUID = 1L;
protected TareaPK clave;
private String descripcion;
.....
}
the controller:
#RequestMapping(value = "/tareas", params = {"clave", "tipot"}, method = RequestMethod.GET)
public String formularioTareaEditar(
#RequestParam(value = "clave") String clave,
#RequestParam(value = "tipot") String tipoTrabajo,
Model model) {
Tarea tarea = catalogoService.getTarea(tipoTrabajo, clave);
model.addAttribute(tarea);
return "tarea/editar";
}
#RequestMapping(value = "/tareas", params = {"clave", "tipot"}, method = RequestMethod.POST)
public String tareaEditar(#Valid #ModelAttribute Tarea tarea, BindingResult result) {
if (result.hasErrors()) {
return "tarea/editar";
} else {
catalogoService.edit(tarea);
return "redirect:/tareas";
}
}
So... when the info gets in the controller the parameter clave is considered as if the object TareaPK of the primary key.
i just change the name of the parameter in my controller.
#RequestMapping(value = "/tareas", params = {"txt_clave", "tipot"}, method = RequestMethod.GET)
public String formularioTareaEditar(...){
...
}

Resources