My Problem is the deserialization of entities from URI-String.
When I use the by Spring Data Rest generated HTTP interface everything works fine.
I can post the following JSON against my endpoint /api/shoppingLists and it will be deserialized to a shopping list with admin as an owner.
{
"name":"Test",
"owners":["http://localhost:8080/api/sLUsers/admin"]
}
When I use a custom RepositoryRestController this doesn't work anymore. If I post exactly the same JSON to the same endpoint I get this response.
{
"timestamp" : "2015-11-15T13:18:34.550+0000",
"status" : 400,
"error" : "Bad Request",
"exception" : "org.springframework.http.converter.HttpMessageNotReadableException",
"message" : "Could not read document: Can not instantiate value of type [simple type, class de.yannicklem.shoppinglist.core.user.entity.SLUser] from String value ('http://localhost:8080/api/sLUsers/admin'); no single-String constructor/factory method\n at [Source: java.io.PushbackInputStream#9cad4d2; line: 1, column: 26] (through reference chain: de.yannicklem.shoppinglist.core.list.entity.ShoppingList[\"owners\"]->java.util.HashSet[0]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class de.yannicklem.shoppinglist.core.user.entity.SLUser] from String value ('http://localhost:8080/api/sLUsers/admin'); no single-String constructor/factory method\n at [Source: java.io.PushbackInputStream#9cad4d2; line: 1, column: 26] (through reference chain: de.yannicklem.shoppinglist.core.list.entity.ShoppingList[\"owners\"]->java.util.HashSet[0])",
"path" : "/api/shoppingLists"
}
My RepositoryRestController:
#RepositoryRestController
#ExposesResourceFor(ShoppingList.class)
#RequiredArgsConstructor(onConstructor = #__(#Autowired ))
public class ShoppingListRepositoryRestController {
private final ShoppingListService shoppingListService;
private final CurrentUserService currentUserService;
#RequestMapping(method = RequestMethod.POST, value = ShoppingListEndpoints.SHOPPING_LISTS_ENDPOINT)
#ResponseBody
#ResponseStatus(HttpStatus.CREATED)
public PersistentEntityResource postShoppingList(#RequestBody ShoppingList shoppingList,
PersistentEntityResourceAssembler resourceAssembler) {
if (shoppingListService.exists(shoppingList)) {
shoppingListService.handleBeforeSave(shoppingList);
} else {
shoppingListService.handleBeforeCreate(shoppingList);
}
return resourceAssembler.toResource(shoppingListService.save(shoppingList));
}
}
Could anybody tell me why the deserialization doesn't work anymore with a custom RepositoryRestController (which is suggested by the docs)?
To take advantage of Spring Data REST’s settings, message converters, exception handling, and more, use the #RepositoryRestController annotation instead of a standard Spring MVC #Controller or #RestController
For the full source code please have a look at the GitHub repo
In order to use the HAL MessageConverter you should have a Resource as a paramter. Try changing your code to:
public PersistentEntityResource postShoppingList(#RequestBody Resource<ShoppingList> shoppingList)
In my case the problem was a difference between the field name at the pojo and the json field. i.e:
#Entity
public class Pojo{
#ManyToOne(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
#JoinColumn(name = "fk_myother")
public Other myOther;
...etc
}
POST -> /dao/pojos
{
"myother":"http://localhost:8034/dao/others/50"
}
pay attention that the json field should be "myOther" instead.
Hope this helps to someone.
Related
I want to validate my request body with #Valid annotation, but it's not working in Spring Boot
I have a Request class within JAR file which I can't modify with two fields. One field is of type Object. My controller class accept this class object as a request body. When I pass my below JSON to the controller, validation is not working. Below are code samples.
Request Class:
public class Request {
Object data;
Map<String, Object> meta;
public <T> T getData() throws ClassCastException {
return (T) this.data;
}
}
Another Class:
public class StudentSignUpRequest {
#NotNull(message = "First Name should not be empty")
#Size(max = 64, message = "FirstName should not exceed 64 characters")
private String firstName;
#NotNull(message = "Last Name should not be empty")
#Size(max = 64, message = "LastName should not exceed 64 characters")
private String lastName;
#NotNull(message = "Email cannot be empty")
#Size(max = 50, message = "Email cannot exceed 50 characters")
#Pattern(regexp = EMAIL_REGEX_PATTERN, message = "Email should contain a valid email address.")
private String email;
// other fields
}
Controller Class:
#PostMapping(value = Constants.STUDENT_SIGN_UP)
public Response signUpStudent(#Valid #RequestBody Request request, HttpServletRequest servletRequest) {
// retrieving the actual resource from request payload
StudentSignUpRequest signUpRequest = request.getData(StudentSignUpRequest.class);
// call service to sign-up student
return loginRegistrationService.signUpStudent(signUpRequest);
}
Calling code sets request as below:
StudentSignUpRequest studentSignUpRequest = new StudentSignUpRequest();
//setter methods
Request payload = new Request();
payload.setData(studentSignUpRequest);
This is the request I am sending:
For more than 64 chars for firstName:
Sample JSON:
{
"data": {
"firstName": "student111111111111111111111111111111111111111111111111111111111111",
"lastName": "somesurname",
"email": "developer#gmail.com"
}
}
Where first name not included:
{
"data": {
"lastName": "somesurname",
"email": "developer#gmail.com"
}
}
Here both #Size as well as #NotNull annotation not working.
Any solution?
Validation would've worked if the Request class was like;
public class Request {
#Valid
StudentSignUpRequest data;
// other stuff
}
The fact that you have no class type for data makes it impossible for validation to be applied on it, ignoring the fact that there isn't even a #Valid annotation on the field. The #Valid annotation is used to propagate the validation cascade.
But since you cannot modify Request object, let's continue with another way to handle validation without doing it manually.
Another way is to trigger validation after you get the StudentSignUpRequest from request object;
StudentSignUpRequest signUpRequest = request.getData(StudentSignUpRequest.class);
loginRegistrationService.signUpStudent(signUpRequest) // validation will trigger with this call
What you can do is as follows;
#Service
#Validated
public class LoginRegistrationService {
public void signUpStudent(#Valid StudentSignUpRequest signUpRequest) {
// some logic
}
}
with #Validated annotation, you will activate the validation check for any #Valid annotated args in public methods within that class.
Can be used with method level validation, indicating that a
specific class is supposed to be validated at the method level (acting
as a pointcut for the corresponding validation interceptor)
This can be costly since you'd want to get any constraint violation as soon as possible without doing any costly jobs for an already doomed request.
No validation will work the way you are using it, you need to put #valid on the object inside your request object, but since you dont have control on that class the other way around is extend Request object and override getData method and apply #valid on that method, it should work that way.
First use #NotEmpty, #Notblank for Strings. Then ensure you import javax.validation.constraints not that of hibernate. If you are using a custom validator, you will need (final BindingResult bindingResult) as part of your controller method variable.
So you can use below code for validating the same.
public <T> T getData() throws ClassCastException, SomeCustomValidationException {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set s = validator.validate(this.data);
//throw SomeCustomValidationException if set is not empty else return this.data
}
A couple of things here:
The type Object for data in Request class makes it impossible for the validator to know that it is of type StudentSignUpRequest. So change the data type.
public class Request {
StudentSignUpRequest data;
Map<String, Object> meta;
}
Secondly, though you have added #Valid in the controller method, in order to validate fields in StudentSignUpRequest you have to add #Valid here as well. Now, data will be validated if passed in the API request. In case it is absent validation won't take place. If you want to make data to be mandatorily passed add #NotNull as well.
public class Request {
#Valid
#NotNull
StudentSignUpRequest data;
Map<String, Object> meta;
}
did you add following dependency?
spring-boot-starter-validation
also check https://www.baeldung.com/spring-boot-bean-validation
I have created one rest API in spring-boot, the problem is that I am taking 4 parameters as a requestbody argument as a JSON object.
Now if I pass the JSON object with 5 parameters than my API still call, no assertion exception occurs while serializing the JSON object.
I have one dto class as below with constructor
public class testDto{
#NotNull(message = "fistName can not be null.")
private String fistName;
#NotNull(message = "lastName can not be null.")
private String lastName;
private String nickName;
public testDto() {
}
public testDto(#NotNull(message = "fistName can not be null.") final String fistName ,#NotNull(message = "lastName can not be null.") final String lastName , final String nickName){
super();
this.fistName = fistName;
this.lastName = lastName;
this.nickName = nickName;
}
}
My restapi as below,
#PostMapping(path ={ "/api/v1/user"}, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> saveUser(
#Valid #RequestBody(required = true) final testDto dto) throws Exception {
}
Now while calling my API I am passing request body as below
{
"fistName" : "abc",
"lastName" : "xyz",
"nickName" : "pqr",
"age" : 25
}
Now here when I pass age parameter and call the API, my API still working instead of throwing an exception for No any constructor found as I have passed age which is not a member of my dto class.
Expected result: don't allow to call API
Actual result: Allow me to call API
I have also tried with assertion exception declaration and binding exception but not getting any success.
also set the property
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Spring Boot uses Jackson to map the JSON payload to an instance of your testDto class. By default, Jackson is configured to ignore entries in the JSON that it cannot map to the DTO. This follows the robustness principle or Postel's law as it's also known.
You can configure Jackson to fail when it cannot map an entry in the JSON to a property on a DTO by adding the following line to your application's application.properties file in src/main/resources:
spring.jackson.deserialization.fail-on-unknown-properties=true
What to do when the request is invalid json but the values are correct ex:
{
"fistName" : "abc",
"lastName" : "xyz",
"nickName" : "pqr",
"nickName" : "pqrs"
}
Now in this case it is taking the last one and mapping it
I am using fasterxml to convert JSON to java.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.3</version>
</dependency>
I am consistently getting 400 Bad Request when I hit the service from UI. Whereas the call works fine from Advanced REST client.
How can I print the JSON that I am received on the backend(java side)?
I tried using the #ControllerAdvice annotation. It is giving me a stacktrace as below. But it does not print the exact input JSON.
13:30:12,563 WARN [com.att.um.controller.ControllerConfig] (default
task-36) Returning HTTP 400 Bad Request:
org.springframework.http.converter.HttpMessageNotReadableException:
Could not read JSON: Can not deserialize instance of
com.att.um.web.services.rest.model.JsonRequest out of START_ARRAY
token at [Source:
io.undertow.servlet.spec.ServletInputStreamImpl#43e804cf; line: 1,
column: 1]; nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Can not
deserialize instance of com.att.um.web.services.rest.model.JsonRequest
out of START_ARRAY token at [Source:
io.undertow.servlet.spec.ServletInputStreamImpl#43e804cf; line: 1,
column: 1] at
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.readJavaType(MappingJackson2HttpMessageConverter.java:228)
[spring-web-4.0.9.RELEASE.jar:4.0.9.RELEASE] at
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.read(MappingJackson2HttpMessageConverter.java:220)
[spring-web-4.0.9.RELEASE.jar:4.0.9.RELEASE] at
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:138)
[spring-webmvc-4.0.9.RELEASE.jar:4.0.9.RELEASE] at
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:178)
[spring-webmvc-4.0.9.RELEASE.jar:4.0.9.RELEASE] at
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:98)
[spring-webmvc-4.0.9.RELEASE.jar:4.0.9.RELEASE]
I think Jackson already does that for you if you annotate your Controller implementation. Ex:
#CrossOrigin
#RestController
public class YourControllerImpl implements YourController {
#Autowired
private YourService yourService;
#Override
public ResponseEntity addObject(#RequestBody Object obj) {
yourService.addObject(obj);
return ResponseEntity.ok().build();
}
}
Method yourService.addObject(obj); can have a backend logic ex:
Object myObj = new Object(obj) or Object myObj = new Object().from(obj) or Object myObj = new Object(); and use getters & setters
And the API might look like:
#RequestMapping("/object")
public interface YourController {
#PostMapping
ResponseEntity addObject(#RequestBody Object obj);
#RequestBody will get your JSON object sent via POST. You can System.out.println(obj) if you want to see what you are sending. You should also implement DTOs.
we are developing a JSON web service to receive data via the #RequestBody annotation. In case a property is included in the request that does not match with the deserialized bean, we expect an HTTP 400 (Bad request) response, but instead the property is simply ignored. Here is an example:
#RestController
#Slf4j
public class TestController {
#RequestMapping(method = RequestMethod.POST, value = "/query")
public void parse(#RequestBody Query query) {
log.info("Received query: {}", query.toString());
}
}
#Data
class Query {
private String from;
private String to;
}
When posting
{ "from" : "123", "to": "456", "foo" : "bar" }
we get a HTTP 200 response. How can we make Spring MVC return HTTP 400 in this case?
Any help or pointers are highly appreciated.
Note that this is different from this question: How to return 400 HTTP error code when some property of a RequestBody parameter is null?.
Since that question asks how to return 400 when an expected property is absent.
Put this into application.properties:
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES=true
Here are the relevant docs: Customize the Jackson ObjectMapper
You can reconfigure your Jackson (assuming you are using it) ObjectMapper to fail on unknown properties.
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
In this case your unknown property will throw JsonMappingException and you can introduce custom exception handler to return in this case Response 400.
How about this -
private String to;
comparing against
{ "from" : "123", "foo" : "bar" }
I think you are comparing to against foo. So deserialization fails.
You can use Validation for this Job , the validator will check if these fields are not null , if they are it spring will return 400 error bad request so here is how you do it:
In your Bean:
public class A {
#NotNull
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
In your Controler:
#ResponseBody
#RequestMapping(path = "/rest")
public B treatRequest(#RequestBody #Validated A a) {...}
In your Configuration File you should activate the validation like this(probably you allready have it):
<mvc:annotation-driven/>
I have the following controller method:
#RequestMapping(value = "/v1/users/{userId}/settings", method = RequestMethod.POST, produces = { "application/json" }, consumes = { "application/json" })
#ResponseStatus(value = HttpStatus.CREATED)
#ResponseBody
public UserSetting createUserSetting(#PathVariable("userId") String userId,
#RequestBody Setting setting){
return userSettingService.createSetting(userId, userSetting);
}
When invoking the url /v1/users/12345/settings POST
with the payload
{"Setting":{"name":"CoolSetting", "id":"SETTING-ID"}}
the method behaves as expected.
What I would also like to accept is:
{"name":"CoolSetting", "id": "SETTING-ID"}
how do I accept the second payload version without the root tag.
The object in question looks like this
public class Setting{
#JsonProperty
private String id;
#JsonProperty
private String name;
//getters and setters removed
}
how can I tell spring to marshel using either json method?
You can use DeserializationConfig.Feature.UNWRAP_ROOT_VALUE property to achieve this result. For serialization use SerializationConfig.Feature.WRAP_ROOT_VALUE.
Detailed explanation and usage available in this blog: Jackson with and without root node