Spring Boot Automatic JSON to Object at Controller - java

I have SpringBoot application with that dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
I have a method at my controller as follows:
#RequestMapping(value = "/liamo", method = RequestMethod.POST)
#ResponseBody
public XResponse liamo(XRequest xRequest) {
...
return something;
}
I send a JSON object from my HTML via AJAX with some fields of XRequest type object (it is a plain POJO without any annotations). However my JSON is not constructed into object at my controller method and its fields are null.
What I miss for an automatic deserialisation at my controller?

Spring boot comes with Jackson out-of-the-box which will take care of un-marshaling JSON request body to Java objects
You can use #RequestBody Spring MVC annotation to deserialize/un-marshall JSON string to Java object... For example.
Example
#RestController
public class CustomerController {
//#Autowired CustomerService customerService;
#RequestMapping(path="/customers", method= RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public Customer postCustomer(#RequestBody Customer customer){
//return customerService.createCustomer(customer);
}
}
Annotate your entities member elements with #JsonProperty with corresponding json field names.
public class Customer {
#JsonProperty("customer_id")
private long customerId;
#JsonProperty("first_name")
private String firstName;
#JsonProperty("last_name")
private String lastName;
#JsonProperty("town")
private String town;
}

SpringBoot by default comes with this functionality. You just have to use #RequestBody annotation in parameter declaration of your controller method but in contrast to #so-random-dude's answer you don't have to annotate fields with #JsonProperty, that is not required.
You just have to provide getters and setters for your custom XML object class. I am posting an example below for simplicity.
Example:
Controller method declaration:-
#PostMapping("/create")
public ResponseEntity<ApplicationResponse> createNewPost(#RequestBody CreatePostRequestDto createPostRequest){
//do stuff
return response;
}
Your custom XML object class:-
public class CreatePostRequestDto {
String postPath;
String postTitle;
public String getPostPath() {
return postPath;
}
public void setPostPath(String postPath) {
this.postPath = postPath;
}
public String getPostTitle() {
return postTitle;
}
public void setPostTitle(String postTitle) {
this.postTitle = postTitle;
}
}

Related

Java RestController REST parameter dependency injection or request

I am trying inject a dependency or at least filtrate the ID parameter that come into a RestControler in Spring. I am very new in Spring. How can i be sure that the parameter that comes passed in the API is valid and/or how can i inject its dependency related to Customer Entity?
This is my rest controller CustomerController method
#PatchMapping("/{id}")
public Customer updateCustomer(#PathVariable Long id, #RequestBody Customer customer) {
return customerService.updateCustomer(id, customer);
}
This is the request that at the moment filtrates only the firstname and last name
package com.appsdeveloperblock.app.ws.requests.customer;
import javax.validation.constraints.NotNull;
public class CreateCustomerRequest {
#NotNull
private String firstname;
#NotNull
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
Thank you!
You need the Bean Validation API (which you probably already have) and it's reference implementation (e.g. hibernate-validator). Check here Java Bean Validation Basics
Summarizing
Add the respective dependencies to your pom.xml (or gradle):
<dependencies>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.1.2.Final</version>
</dependency>
</dependencies>
Use #Valid annotation on your Customer entity to have the payload validated automatically:
#PatchMapping("/{id}")
public Customer updateCustomer(#PathVariable Long id, #RequestBody #Valid Customer customer) {
return customerService.updateCustomer(id, customer);
}
You can decorate the fields of your Customer or CreateCustomerRequest class with further annotations, e.g. #Size, #Max, #Email etc. Check the tutorial for more information.

Spring boot and Bean validation in different methods and the same class

I'm doing a rest webservice with spring boot and I would like to know if is possible do different validations with bean validation annotations by method with a POJO as param in the controller layer.
example:
POJO:
Public class Person{
#NotNull(forMethod="methodOne")
private String firstName;
#NotNull(forMehotd="methodTwo")
private String lastName;
private String age;
//getter and setter
}
Controller
#RestController
public class controller{
#RequestMapping(....)
public ResponseEntity methodOne(#Valid #RequestBody Person person){
.....
}
#RequestMapping(....)
public ResponseEntity methodTwo(#Valid #RequestBody Person person){
......
}
}
I know that is possible do it with separate parameters in the methods, but I have a POJO with so many attributes. is it possible do something like that?
I think you should use validation groups in your bean validation annotations and use #Validated annotation instead of #Valid annotation. because #Validated annotation has a value properties that specifies a group for validation.
for example:
Public class Person{
#NotNull(groups={MethodOne.class})
private String firstName;
#NotNull(groups={MethodTwo.class})
private String lastName;
private String age;
//getter and setter
}
and
#RestController
public class controller{
#RequestMapping(....)
public ResponseEntity methodOne(#Validated(MethodOne.class) #RequestBody Person person){
.....
}
#RequestMapping(....)
public ResponseEntity methodTwo(#Validated(MethodTwo.class) #RequestBody Person person){
......
}
}
by the way, don't forget that you should create MethodOne and MethodTwo interfaces to use them as your validation groups.

How to fix MessageBodyProviderNotFoundException with Jersey Client using Jackson?

I have set up a REST client consuming JSON with Jersey. Firstly according to Baeldung's tutorial with MOXy and secondly according to Vaadin's tutorial with Jackson.
The JSON response from the REST service looks as follows:
{
"DisplayName": "Sixpack, Joe",
"UserID": "joe.sixpack",
"StdLog": "Some text"
}
So I have set up a simple JAXB bean ...
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class DisplayName {
private String displayName;
private String userID;
private String stdLog;
public DisplayName() {
}
public DisplayName(String DisplayName, String UserID, String StdLog) {
this.displayName = DisplayName;
this.userID = UserID;
this.stdLog = StdLog;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getStdLog() {
return stdLog;
}
public void setStdLog(String stdLog) {
this.stdLog = stdLog;
}
}
... added the Maven dependencies ...
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.22.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.13</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>
... and implemented a REST client:
Client client = ClientBuilder.newClient();
URI uri = UriBuilder.fromUri("http://server:2000").path("/path/to/service/" + UriComponent.encode(input_parameter, UriComponent.Type.QUERY_PARAM_SPACE_ENCODED)).build();
WebTarget target = client.target(uri);
DisplayName responseDN = target.request(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).get(DisplayName.class);
However, all the POJO fields are always null when using the MOXy media converter regardless of the annotations and constructors (e.g. no-args constructor) used according to similar issues (e.g. JAX-RS Response Object displaying Object fields as NULL values, Java REST service accepts POJO, but fields are always null).
So I want to use Jackson and adapted the JAXB class ...
import com.fasterxml.jackson.annotation.*;
#JsonInclude(JsonInclude.Include.ALWAYS)
#JsonPropertyOrder({"DisplayName","UserID","StdLog"})
public class DisplayName {
#JsonProperty("DisplayName")
private String displayName;
#JsonProperty("UserID")
private String userID;
#JsonProperty("StdLog")
private String stdLog;
public DisplayName() {
}
#JsonCreator
public DisplayName(#JsonProperty("DisplayName") String DisplayName, #JsonProperty("UserID") String UserID, #JsonProperty("StdLog") String StdLog) {
this.displayName = DisplayName;
this.userID = UserID;
this.stdLog = StdLog;
}
public String getDisplayName() {
return displayName;
}
#JsonProperty("DisplayName")
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getUserID() {
return userID;
}
#JsonProperty("UserID")
public void setUserID(String userID) {
this.userID = userID;
}
public String getStdLog() {
return stdLog;
}
#JsonProperty("StdLog")
public void setStdLog(String stdLog) {
this.stdLog = stdLog;
}
}
... and removed the MOXy dependency and added a dependency for JAXB:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
However, now I run into the error
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=application/json
and none of the solutions I found did help (e.g. MessageBodyReader not found for media type=application/json, Glassfish :MessageBodyProviderNotFoundException in Jersy Client).
Why? And what is the solution?
Shortly after I had posted the question, I found the explanation and solution in the Jersey User Guide which says:
In order to use Jackson as your JSON (JAXB/POJO) provider you need to register JacksonFeature and a ContextResolver for ObjectMapper, if needed, in your Configurable (client/server).
Since MOXy is the default media library and registered automatically with the WebClient, it will be used unless another media library is registered. Hence, one has to register the Jackson media converter ...
Client client = ClientBuilder.newClient().register(JacksonFeature.class);
... and add the respective Maven dependency:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
I'm just surprised that neither the tutorials nor the other answers to similar questions mentioned it.

Why object to json conversion using spring MVC doesn't work in this case?

I need to execute this URL: http://localhost:8080/FitiProject/student and the response needs to be a json string containing the data of a Student object.
Here is my code:
package spring.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import model.Student;
#Controller
public class PruebaController {
#RequestMapping("/student")
public #ResponseBody Student getStudent(){
return new Student(2, "h");
}
}
This is Student.java
package model;
public class Student {
private int edad;
private String nombre;
public Student(int edad, String nombre) {
super();
this.edad = edad;
this.nombre = nombre;
}
public int getEdad() {
return edad;
}
public void setEdad(int edad) {
this.edad = edad;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
}
When I do a GET request on the URL, I don't get a JSON response instead I get a 406 error code. How can I solve this?
I'm using Jackson 1.9 and Spring 4.1.
Your getStudent method lacks the content type of the response, otherwise Spring won't know to which format convert the Student. This can be done by using produces attribute in #RequestMapping.
//"produces" will tell Spring to which format convert the data
//"method" will tell Spring which HTTP method should be handled for this URL
#RequestMapping(value="/student",
produces="application/json; charset=UTF-8",
method=RequestMethod.GET)
public #ResponseBody Student getStudent(){
return new Student(2, "h");
}
When executing a request to your URL, make sure that the client uses the following header: Content-Type: application/json
It's worth to mention that your project needs to have jackson libraries in order to work.
If you're already using Jackson, you could try using the ObjectMapper class:
ObjectMapper mapper = new ObjectMapper();
System.out.println("Object in JSON:\n" + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object));
Since you are using #ResponseBody annotation the response will be automatically converted to JSON. Make sure you include jackson mapper library in your class path.
If you are using Maven, you can add the jackson dependency as follows :
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.8.2</version>
</dependency>
Use the RestController : org.springframework.web.bind.annotation.RestController to get automatic conversion of the responseBody into a JSON Format.

How do I make Spring call a validator on a REST service?

I have studied many, many answers and I still can't see what I'm doing wrong.
I have a REST service controller which takes a bean as a parameter and I would like to validate that the fields on the bean are properly set.
The following is (a portion of) my configuration object.
#Configuration
#ComponentScan({...})
#EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
#Bean
public Validator validator() {
LocalValidatorFactoryBean validatorBean = new LocalValidatorFactoryBean();
return validatorBean;
}
#Override
public Validator getValidator() {
return validator();
}
}
I can't tell if those are necessary, but I can tell they're never called, so maybe they're in the wrong place?
The following is (a portion of) my pom.xml:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>5.1.1.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
I don't think el is necessary for what I'm doing, but I included it in case it made a difference.
Here is (a portion of) my bean class that needs to be validated:
public class ContactInfo {
#NotNull
#Size(min=1, max=50)
private String phone;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
And finally, this is (a portion of) the Rest Controller:
#RestController
#RequestMapping("/contact")
public class ContactController {
private static final String USER_ID_ATTRIBUTE = "userId";
#Autowired
private Validator validator;
#Autowired
#Qualifier("contactService")
private ContactService service;
#RequestMapping("/submit")
public void submitContact(
#Valid #RequestBody ContactInfo formData, BindingResult result) throws Exception {
assert(validator != null);
if(result.hasErrors()) {
throw new Exception("I don't like it one bit.");
}
service.submitContact(formData);
}
}
In the debugger, I can see that the validator passed in is an OptionalValidatorFactoryBean, and result.hasErrors() is always false.
The JSON passed in to the controller includes this:
{ "phone":"" }
I think that should trigger a validation error because it doesn't meet the minimum size.

Categories