Spring MVC wrap a lot of #RequestParam to an Object [duplicate] - java

Suppose i have a page that lists the objects on a table and i need to put a form to filter the table. The filter is sent as an Ajax GET to an URL like that: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z
And instead of having lots of parameters on my Controller like:
#RequestMapping(value = "/action")
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
#RequestParam(value = "prop1", required = false) String prop1,
#RequestParam(value = "prop2", required = false) String prop2,
#RequestParam(value = "prop3", required = false) String prop3) { ... }
And supposing i have MyObject as:
public class MyObject {
private String prop1;
private String prop2;
private String prop3;
//Getters and setters
...
}
I wanna do something like:
#RequestMapping(value = "/action")
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
#RequestParam(value = "myObject", required = false) MyObject myObject,) { ... }
Is it possible?
How can i do that?

You can absolutely do that, just remove the #RequestParam annotation, Spring will cleanly bind your request parameters to your class instance:
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
MyObject myObject)

I will add some short example from me.
The DTO class:
public class SearchDTO {
private Long id[];
public Long[] getId() {
return id;
}
public void setId(Long[] id) {
this.id = id;
}
// reflection toString from apache commons
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
Request mapping inside controller class:
#RequestMapping(value="/handle", method=RequestMethod.GET)
#ResponseBody
public String handleRequest(SearchDTO search) {
LOG.info("criteria: {}", search);
return "OK";
}
Query:
http://localhost:8080/app/handle?id=353,234
Result:
[http-apr-8080-exec-7] INFO c.g.g.r.f.w.ExampleController.handleRequest:59 - criteria: SearchDTO[id={353,234}]
I hope it helps :)
UPDATE / KOTLIN
Because currently I'm working a lot of with Kotlin if someone wants to define similar DTO the class in Kotlin should have the following form:
class SearchDTO {
var id: Array<Long>? = arrayOf()
override fun toString(): String {
// to string implementation
}
}
With the data class like this one:
data class SearchDTO(var id: Array<Long> = arrayOf())
the Spring (tested in Boot) returns the following error for request mentioned in answer:
"Failed to convert value of type 'java.lang.String[]' to required type
'java.lang.Long[]'; nested exception is
java.lang.NumberFormatException: For input string: \"353,234\""
The data class will work only for the following request params form:
http://localhost:8080/handle?id=353&id=234
Be aware of this!

Since the question on how to set fields mandatory pops up under each post, I wrote a small example on how to set fields as required:
public class ExampleDTO {
#NotNull
private String mandatoryParam;
private String optionalParam;
#DateTimeFormat(iso = ISO.DATE) //accept Dates only in YYYY-MM-DD
#NotNull
private LocalDate testDate;
public String getMandatoryParam() {
return mandatoryParam;
}
public void setMandatoryParam(String mandatoryParam) {
this.mandatoryParam = mandatoryParam;
}
public String getOptionalParam() {
return optionalParam;
}
public void setOptionalParam(String optionalParam) {
this.optionalParam = optionalParam;
}
public LocalDate getTestDate() {
return testDate;
}
public void setTestDate(LocalDate testDate) {
this.testDate = testDate;
}
}
//Add this to your rest controller class
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String testComplexObject (#Valid ExampleDTO e){
System.out.println(e.getMandatoryParam() + " " + e.getTestDate());
return "Does this work?";
}

I have a very similar problem. Actually the problem is deeper as I thought. I am using jquery $.post which uses Content-Type:application/x-www-form-urlencoded; charset=UTF-8 as default. Unfortunately I based my system on that and when I needed a complex object as a #RequestParam I couldn't just make it happen.
In my case I am trying to send user preferences with something like;
$.post("/updatePreferences",
{id: 'pr', preferences: p},
function (response) {
...
On client side the actual raw data sent to the server is;
...
id=pr&preferences%5BuserId%5D=1005012365&preferences%5Baudio%5D=false&preferences%5Btooltip%5D=true&preferences%5Blanguage%5D=en
...
parsed as;
id:pr
preferences[userId]:1005012365
preferences[audio]:false
preferences[tooltip]:true
preferences[language]:en
and the server side is;
#RequestMapping(value = "/updatePreferences")
public
#ResponseBody
Object updatePreferences(#RequestParam("id") String id, #RequestParam("preferences") UserPreferences preferences) {
...
return someService.call(preferences);
...
}
I tried #ModelAttribute, added setter/getters, constructors with all possibilities to UserPreferences but no chance as it recognized the sent data as 5 parameters but in fact the mapped method has only 2 parameters. I also tried Biju's solution however what happens is that, spring creates an UserPreferences object with default constructor and doesn't fill in the data.
I solved the problem by sending JSon string of the preferences from the client side and handle it as if it is a String on the server side;
client:
$.post("/updatePreferences",
{id: 'pr', preferences: JSON.stringify(p)},
function (response) {
...
server:
#RequestMapping(value = "/updatePreferences")
public
#ResponseBody
Object updatePreferences(#RequestParam("id") String id, #RequestParam("preferences") String preferencesJSon) {
String ret = null;
ObjectMapper mapper = new ObjectMapper();
try {
UserPreferences userPreferences = mapper.readValue(preferencesJSon, UserPreferences.class);
return someService.call(userPreferences);
} catch (IOException e) {
e.printStackTrace();
}
}
to brief, I did the conversion manually inside the REST method. In my opinion the reason why spring doesn't recognize the sent data is the content-type.

While answers that refer to #ModelAttribute, #RequestParam, #PathParam and the likes are valid, there is a small gotcha I ran into. The resulting method parameter is a proxy that Spring wraps around your DTO. So, if you attempt to use it in a context that requires your own custom type, you may get some unexpected results.
The following will not work:
#GetMapping(produces = APPLICATION_JSON_VALUE)
public ResponseEntity<CustomDto> request(#ModelAttribute CustomDto dto) {
return ResponseEntity.ok(dto);
}
In my case, attempting to use it in Jackson binding resulted in a com.fasterxml.jackson.databind.exc.InvalidDefinitionException.
You will need to create a new object from the dto.

Yes, You can do it in a simple way. See below code of lines.
URL - http://localhost:8080/get/request/multiple/param/by/map?name='abc' & id='123'
#GetMapping(path = "/get/request/header/by/map")
public ResponseEntity<String> getRequestParamInMap(#RequestParam Map<String,String> map){
// Do your business here
return new ResponseEntity<String>(map.toString(),HttpStatus.OK);
}

Accepted answer works like a charm but if the object has a list of objects it won't work as expected so here is my solution after some digging.
Following this thread advice, here is how I've done.
Frontend: stringify your object than encode it in base64 for submission.
Backend: decode base64 string then convert the string json into desired object.
It isn't the best for debugging your API with postman but it is working as expected for me.
Original object: { page: 1, size: 5, filters: [{ field: "id", value: 1, comparison: "EQ" }
Encoded object: eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiY29tcGFyaXNvbiI6Ik5VTEwifV19
#GetMapping
fun list(#RequestParam search: String?): ResponseEntity<ListDTO> {
val filter: SearchFilterDTO = decodeSearchFieldDTO(search)
...
}
private fun decodeSearchFieldDTO(search: String?): SearchFilterDTO {
if (search.isNullOrEmpty()) return SearchFilterDTO()
return Gson().fromJson(String(Base64.getDecoder().decode(search)), SearchFilterDTO::class.java)
}
And here the SearchFilterDTO and FilterDTO
class SearchFilterDTO(
var currentPage: Int = 1,
var pageSize: Int = 10,
var sort: Sort? = null,
var column: String? = null,
var filters: List<FilterDTO> = ArrayList<FilterDTO>(),
var paged: Boolean = true
)
class FilterDTO(
var field: String,
var value: Any,
var comparison: Comparison
)

Related

Java #requestBody doesn't work, dto empty

For some reason java can't map DTO with requestBody and all values are default ones, as for request it works, with payload for ex. "{"productId":1,"commitment":6,"returnMonths":"2"}"
DTO
#Data
public class Request {
private int productId;
private int commitment;
private String returnMonths;
// contructers
}
Controller :
#PostMapping(value = "/calculate", consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String calculatePrice(#RequestBody Request request) {
productService.calculatePrice(request);
return "Success";
}
front request:
submit: async function() {
let request = {
productId: this.productSelected,
commitment: this.optionSelected,
returnMonths: this.input
};
let data = await getCalculation(request);
console.log(data);
}
DTO maps as:
productId : 0
commitment : 0
returnMonths : null
Tried an exact copy of your code and it worked when tested with Postman. This makes me think it's either something to do with the FE or maybe some issue in the service. I'd check if the Frontend really sends the data.
Try to annotation Request class with #AllArgsConstructor like:
#AllArgsConstructor
#Data
public class Request {
private int productId;
private int commitment;
private String returnMonths;
}
If your request body contains properties that is date such as LocalDateTime, make sure to format it in your DTO using #JsonFormat(pattern="") respecting the input value.

How to change declared body type from String to custom DTO type in Swagger using SpringBoot and SpringFox

I have a rest controller with one method. This method takes one String argument annotated as #RequestBody. For some reason not mentioned here, I'm forced to use type String and manually convert it to TestDTO. From the API's consumer point of view body is type of TestDTO and I want to show this type in SwaggerUI.
Unfortunately (which is quite obvious) swagger shows that body is type of String. Look at the picture below.
What I want to achieve is to have String body in java code and TestDTO in swagger code. How can I force Swagger to show it? I tried to find annotations and its properties, but failed.
Rest controller code below:
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
class TestDTO{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
I figured it out. Two changes need to be made.
First, like in #Dave Pateral's answer #ApiImplicitParams must be added
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#ApiImplicitParams({
#ApiImplicitParam(name = "requestBody", required = true,
dataType = "TestDTO", paramType = "body")
})
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
And then implicit Model must be registered in the docket, minimal working example below
#Configuration
public class SwaggerConfiguration {
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.additionalModels(typeResolver.resolve(TestDTO.class));
}
}
And the result is
Try put this annotation on your method:
#ApiImplicitParam(name = "test", value = "testDTO", required = true, dataType = "TestDTO")

HTTP Status 400 - Required String parameter 'walletName' is not present

I work with Java/ Spring MVC RESTful app and get 400 HTTP status error while doing a POST request. The #RestController method is provided,
#RequestMapping(value = "/generateAddress", method = RequestMethod.POST)
public ResponseEntity<WalletInfoWrapper> generateAddress(#RequestParam("walletName") String walletName,
#RequestParam("currencyName") String currencyName) {
logger.info("walletName {} and currencyName {}", walletName, currencyName);
// return if the wallet name or the currency is null
if (Objects.isNull(walletName) || Objects.isNull(currencyName)) {
return new ResponseEntity<WalletInfoWrapper>(HttpStatus.NOT_ACCEPTABLE);
}
WalletInfo walletInfo = walletService.generateAddress(walletName, currencyName);
if (Objects.isNull(walletInfo)) {
return new ResponseEntity<WalletInfoWrapper>(HttpStatus.NOT_ACCEPTABLE);
}
WalletInfoWrapper walletInfoWrapper = new WalletInfoWrapper();
walletInfoWrapper.setName(walletInfo.getName());
return new ResponseEntity<WalletInfoWrapper>(walletInfoWrapper, HttpStatus.CREATED);
}
The POST request in the Postman provided below,
The error message informs, Required String parameter 'walletName' is not present
I can also provide the code for the services and the dataase layers for observing the drop-down operations. What is the issue here?
UPDATE
I updated the Postman request like this and still having the same error,
UPDATE 1
I still have the same issue,
I POST with the data,
{"walletName":"puut","currencyName":"Bitcoin"}
The code is provided below,
#RequestMapping(value = "/generateAddress", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<WalletInfoWrapper> generateAddress(#RequestBody WalletWithMoneyRequest walletWithMoneyRequest) {
String walletName = walletWithMoneyRequest.getWalletName();
String currencyName = walletWithMoneyRequest.getCurrencyName();
logger.info("walletName {} and currencyName {}", walletName, currencyName);
// return if the wallet name or the currency is null
if (Objects.isNull(walletName) || Objects.isNull(currencyName)) {
return new ResponseEntity<WalletInfoWrapper>(HttpStatus.NOT_ACCEPTABLE);
}
WalletInfo walletInfo = walletService.generateAddress(walletName, currencyName);
if (Objects.isNull(walletInfo)) {
return new ResponseEntity<WalletInfoWrapper>(HttpStatus.NOT_ACCEPTABLE);
}
WalletInfoWrapper walletInfoWrapper = new WalletInfoWrapper();
walletInfoWrapper.setName(walletInfo.getName());
return new ResponseEntity<WalletInfoWrapper>(walletInfoWrapper, HttpStatus.CREATED);
}
The POJO is provided,
private class WalletWithMoneyRequest {
String walletName;
String currencyName;
public WalletWithMoneyRequest(String walletName, String currencyName) {
this.walletName = walletName;
this.currencyName = currencyName;
}
public WalletWithMoneyRequest() {
}
public String getWalletName() {
return walletName;
}
public String getCurrencyName() {
return currencyName;
}
public void setCurrencyName(String currencyName) {
this.currencyName = currencyName;
}
public void setWalletName(String walletName) {
this.walletName = walletName;
}
}
This is the error message,
Here ia the Tomcat server info,
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver: 08/19/2017 19:45:55 - Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `mobi.puut.controllers.WalletRestController$WalletWithMoneyRequest` (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument constructor; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `mobi.puut.controllers.WalletRestController$WalletWithMoneyRequest` (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument constructor
at [Source: (PushbackInputStream); line: 1, column: 2]
Tomcat Localhost log
Tomcat Catalina log
Edit
In your Postman request, instead of sending JSON, send the values as x-www-form-urlencoded.
Your controller is expecting 2 request parameters that normally look like this: /someurl?walletName=my-wallets-name&currencyName=dollars.
You're sending a json string in the post body, but no formal parameters. You need to update either your POST, or your controller to make the two ends agree. I think you probably want to replace the two #RequestParam annotated Strings, with a Java pojo that has two String members: walletName and currencyName, drop that pojo in your request method as an argument and precede it with the annotation #RequestBody. This will match your json post.
To have your controller accept the post with JSON in the body edit it like this:
#RequestMapping(value = "/generateAddress", method = RequestMethod.POST)
public ResponseEntity<WalletInfoWrapper> generateAddress(#RequestBody
WalletWithMoneyRequest myJsonRequestComingIn) {
logger.info("walletName {} and currencyName {}", myJsonRequestComingIn.getWalletName(), myJsonRequestComingIn.getCurrencyName());
And your pojo
public class WalletWithMoneyRequest{
private String walletName;
private String currencyName;
//getters and setters down here.
To elaborate on zerpsed's answer.
Change the signature to:
public ResponseEntity<WalletInfoWrapper> generateAddress(#ResponseBody WalletPOJO walletCurrency)
Where the WalletPOJO has the two fields walletName and currencyName
I believe the issue is solved for now. I have used a POJO as suggested with the #RequestBody parameter in the RESTful method. The catch here is I need to make the POJO out of the class (in the same file though) and later, put in the entity directory as an entity.
class WalletWithMoneyRequest {
String walletName;
String currencyName;
public WalletWithMoneyRequest(String walletName, String currencyName) {
this.walletName = walletName;
this.currencyName = currencyName;
}
public WalletWithMoneyRequest() {
}
public String getWalletName() {
return walletName;
}
public String getCurrencyName() {
return currencyName;
}
public void setCurrencyName(String currencyName) {
this.currencyName = currencyName;
}
public void setWalletName(String walletName) {
this.walletName = walletName;
}
}
The main issue is believe was an error in the HQL,
I wrote currency =: currency where it should be currency = :currency
I still can't have the data in the database as I will need to modify the method in the database layer.
En POSTMAN set variables in params

Spring: Bind a collection on controller parameter

I've seen lots of questions similar to mine but, I couldn't find a solution to this problem so far.
I am implementing a grid filtering and pagination on Spring + Hibernate. The load() method must receive the specific parameters for pagination (page, start and limit) and a list of key-value parameter for filtering, which is being the problem.
The parameters are coming like that:
page:1
start:0
limit:23
filter:[{"operator":"like","value":"tes","property":"desc"},{"operator":"like","value":"teste","property":"model_desc"}]
or (encoded version):
page=1&start=0&limit=23&filter=%5B%7B%22operator%22%3A%22like%22%2C%22value%22%3A%22tes%22%2C%22property%22%3A%22desc%22%7D%2C%7B%22operator%22%3A%22like%22%2C%22value%22%3A%22teste%22%2C%22property%22%3A%22model_desc%22%7D%5D
The filter parameter is coming as a String and the problem is to make Spring parse that either as something like ArrayList<Map<String,String>> or ArrayList<SomeFilterClass>.
This is the signature of my controller method (the commented lines are all not working, they are here just to show what I've tried so far):
public Map<String, Object> loadData(#RequestParam(value = "page", required = true) int page,
#RequestParam(value = "start", required = true) int start,
#RequestParam(value = "limit", required = true) int limit,
// #ModelAttribute("filter") ArrayList<Map<String, String>> filter) {
// #RequestParam(value = "filter", required = false) Map<String, Object>[] filter) {
// #RequestParam(value = "filter", required = false) List<Map<String, String>> filter) {
#ModelAttribute("filter") RemoteFilter filter)
This class, RemoteFilter, is a wrapper class that I built, following a suggestion from other posts but, it didn't work also. Its structure is:
public class RemoteFilter {
private ArrayList<Filter> filter;
//Getters and Setters....
class Filter {
private String operator;
private String value;
private String property;
//Getters and Setters....
}
}
I will be very glad if anybody help me with that.
Thanks!
Try to POST the data instead of using GET, Spring only offers JSON to Java conversion when data is posted.
Post
{
page:1
start:0
limit:23
filter:[{"operator":"like","value":"tes","property":"desc"},{"operator":"like","value":"teste","property":"model_desc"}]
}
And have the controller use #RequestBody
#RequestMapping(method = RequestMethod.POST, value = "url",
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE)
public Map<String, Object> loadData(#RequestBody RemoteFilter filter) {
}
The response uses Objectas the Map value type. This will work, but using an un-typped return value is a bad thing in general.

How to implement custom jackson object mappers

I'm working on adapting angular-ui-tree to persist the tree structure in a one-to-many entity model. The piece I am missing is how to have Spring/Jackson interpret the following POST body
data: "{
"id":1.7976931348623157e+308,
"name":"New Category -- Need Name",
"parent":6,
"type":"create"
}"
that is serialized and delivered using a $http service method in Angular:
$http.post("/rest/category",
{data: angular.toJson(updateData)}
)
.success(function(data, status, headers, config){
return [data, status];
})
.error(function(data, status, headers, config){
return [data, status];
})
Right now, I only get error 405s with the address, with the return value stating that
Request method 'POST' not supported
So, I modified the controller below to take in a DummyCategory object from the RequestBody:
#RequestMapping(value = "/rest/category", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public List<Category> createCategory(#RequestBody DummyCategory data ){
log.info("Start creating category " + data);
}
which is this entity:
#JsonDeserialize(using = DummyCategoryDeserializer.class)
public class DummyCategory {
private String type;
private String name;
private Long id;
private int parent;
//getters and setters
}
and corresponding DummyCategoryDeserializer class:
public class DummyCategoryDeserializer extends JsonDeserializer<DummyCategory> {
#Override
public DummyCategory deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
String tempData = jp.getText();
DummyCategory dummyCategory = new DummyCategory();
return dummyCategory;
}
}
Since I am very new at this, my goal is to have a debugger breakpoint set at the deserializer method and work my way through the properties. However, after making these custom modifications, the $http service is still returning 405s on execution, and the breakpoints are not being hit.
I know that the server is functional, because all GET requests are returning expected values.
If you want to see the actual source code for this question, it is available on the category-taxonomy branch. The new deserializer classes are within /models and /utils

Categories