I have the following method and I need to pass List<UUID> to this Controller method.
#GetMapping("/product/{productUuidList}")
public ResponseEntity<ApiResponse<List<ProductDTO>>> getProductList(
#RequestParam List<UUID> productUuidList) {
// code omitted
}
If I pass the list parameters by separating comma, it is ok. But, I want to pass these list parameters as Json array. When I try to pass uuid parameters in Postman as shown below, I get "productUuidList parameter is missing" error.
{
"productUuidList": ["c849bcbb-c26c-4299-9ca4-dcde56830f5f", "398ec0f8-86c8-400a-93cb-caf47c1ac92d"]
}
So, how should I properly pass uuid array to this Controller method without changing #GetMapping to #PostMapping ?
It won't work like this. Here you are sending a JSON object that looks like this java POJO :
public class MyUUIDWrapper{
private List<UUID> productUuidList;
// GETTERS/SETTERS
}
So Spring is expecting to retrieve a MyUUIDWrapper object in the RequestBody
If you change your code like this it should work :
public ResponseEntity<ApiResponse<List<ProductDTO>>> getProductList(
#RequestParam MyUUIDWrapper uuids) {
NB : If you have troubles deserializing UUIDs (I've never done it before), change List to List. Not the smartest or most beautiful solution, but you can still convert them later in your controller ;)
Related
I'm writing a REST API using Spring and have certain clients to the service that cannot or will not change how they call my service.
Normally when sending a query param with a list of values you would just comma delimit the parameter and Spring will do the rest
curl http://host.com/api/endpoint?listParam=1,2,3
And the controller
#GetMapping("/api/endpoint")
public ResponseEntity endpoint(#RequestParam("listParam" List<String> listParam){
// Here, listParam is populated with 1,2,3
}
Unfortunately my clients are going to be passing lists with the bar | delimiter and it simply isn't possible to get them to change that.
Example: curl http://host.com/api/endpoint?listParam=1%7C2%7C3%7C
I would still like to use Spring to break these calls out into lists so I don't have to clutter my code with manual String.split() calls.
What I've already tried:
I found the #InitBinder annotation and wrote the following
#InitBinder
public void initBinder(WebDataBinder dataBinder){
dataBinder.registerCustomEditor(String[].class, new StringArrayPropertyEditor("|"));
}
However, this code doesn't seem to ever be called (watching with breakpoints) and requests using the bar as the delimiter fail with a 400 BAD REQUEST.
Any suggestions would be much appreciated, thanks!
404 is coming due to URL encoding issue.
You need to encode | then it will work, but it will create another problem, params would not be split.
To work around this you need to create a custom conversion that can convert String to Collection. For the custom conversion, you can check the StringToCollectionConverter class. Once you have custom conversion then you can register that service, in any of the configuration classes add following function
#Autowired
void conversionService(GenericConversionService genericConversionService) {
genericConversionService.addConverter(myStringToCollectionConvert());
}
#Bean
public MyStringToCollectionConvert myStringToCollectionConvert() {
return new MyStringToCollectionConvert();
}
In this MyStringToCollectionConvert is class that will parse String and converts to a collection of Strings.
I've accepted Sonus21's answer since his suggestion allowed me to hunt down an example that worked, but my solution was not exactly his.
The class StringToCollectionConverter did in fact exist for me, but it wasn't accessible and I couldn't use it in any way. However, in looking at the interface it implemented (ConditionalGenericConverter) and searching for more examples with Spring converters I eventually settled on the following solution.
The listParam in my question actually refers to a set of Enum values. The first thing I did was rewrite my controller to actually use the Enum values instead of raw Integers.
#GetMapping("/api/endpoint")
public ResponseEntity endpoint(#RequestParam("listParam" List<EnumClass> listParam){
// ...
}
Next, I wrote a Spring Custom Converter (Baeldung Doc)
public class CustomStringToEnumClassListConverter implements Converter<String, List<EnumClass>> {
#Override
public List<EnumClass> convert(String str) {
return Stream.of(
str.split("\\|")) // Here is where we manually delimit the incoming string with bars instead of commas
.map(i -> EnumClass.intToValue(Integer.parseInt(i))) // intToValue is a method I wrote to get the actual Enum for a given int
.collect(Collectors.toList());
}
}
Finally, I wrote a Config Bean and registered this Custom Converter with Spring:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new CustomStringToEnumClassListConverter());
}
}
Once all of this was done, Spring automatically populated the listParam list with EnumClass objects.
In my Spring web application, I have an API that accepts requests with application/x-www-form-urlencoded content type.
#RequestMapping(value = "/do-it", method = {RequestMethod.POST})
public String test(#ModelAttribute("request")RequestDTO request,HttpServletRequest
httpServletRequest, Map<String, Object> model, RedirectAttributes redirectAttributes){
.....
}
My RequestDTO has following fields in it.
public class RequestDTO {
private String paramOne;
private String paramTwo;
// standard getters and setters
}
This implementation works fine, all the request params get mapped to the request dto as expected. However, now I have this requirement to accept the requests with the fields in following pattern.
param_one, param_two
I understand that, using #JsonProperty annotation on the fields in my request dto is not gonna work in my case since the request is not in the type of application/json.
The only way I have found to solve the issue is creating new setters like following (which looks ugly in my opinion when it comes to naming convention).
public void setParam_one(String param_one) {
this.paramOne = param_one;
}
Can some one help me to find a better way to get this done? I cannot change the param names in original request dto.
Thank you..!
I was able to get this done. Thanks to #neetash for guiding me.
Everything I needed to have was a Custom HandlerMethodArgumentResolver to map the post request body data to the object that I wanted to get.
I followed following linked tutorial to implement it. It contains every single thing someone needs to know to create a HandlerMethodArgumentResolver.
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-creating-a-custom-handlermethodargumentresolver/
Suppose I want to handle patch requests and write code like that:
#RequestMapping(value = "/{id}",
method = RequestMethod.PATCH)
#Consumes("application/json")
#Produces("application/json")
public SomeResponse updateType(#PathVariable String id,
#RequestBody ContactDto contact) throws Throwable {
//extract parameters from contact
}
So the problem is how recognize what parameters are set from patch requests? At first look it is obvious to write like this:
if (contact.getHobby() != null) {
updateHobby(contact.getHobby());
}
But what if I want to set hobby to null via json or delete it from current contact? So I pass a json string without hobby property and upper code does not work.
So the problem is - how separate properties that are set to null from those which are not set at all?
The body of a PATCH request should describe the changes to be made on the target entity. You shouldn't pass the entity itself.
With PATCH, however, the enclosed entity contains a set of
instructions describing how a resource currently residing on the
origin server should be modified to produce a new version.
For example, if you wanted to remove the hobby, you could pass some JSON like
{"removeHobby":true}
Your application would parse that and know it had to alter the entity by removing its hobby value.
I'm thinking the answer here is probably no, but just in case.
I'm doing something like this:
#RequestMapping(value="data.json", params="query=overview")
public String getOverview(#RequestBody MyRequest myRequest) {
[...]
return "overview";
}
#RequestMapping(value="data.json", params="query=detail")
public String getDetail(#RequestBody MyRequest myRequest) {
[...]
return "detail";
}
and the client is POSTing JSON data, which is deserialized by Jackson on the way in and bound to the MyRequest parameter, all working nicely.
However, I don't like the query type having to be specified in the URL of the requests. What I would like is to include the query parameter in the JSON object and use that to drive the #RequestMapping. Is this possible?
If not, I guess I will implement a single mapping and have that method delegate to others based on the incoming data, but that feels like a step in the wrong direction.
What you are trying to do does not work out of the box.
If you don't like the param why don't you add the qualifier to the URL like so:
#RequestMapping("/overview/data.json")
#RequestMapping("/detail/data.json")
If you absolutely need the functionality you mention, you could implement a custom RequestMappingHandlerMapping that would do what you want by extending that class as is done here.
It's not possible if you remove the params. You have to have something distinct between the two mappings. If you are intent on getting rid of the params, best you could do is have a single method/mapping and call your services or whatever other logic you have according to what the value of query is in your MyRequest object.
#RequestMapping(value="data.json")
public String getOverviewOrDetail(#RequestBody MyRequest myRequest) {
if (myRequest.getQuery().equalsIgnoreCase("overview")) {
[...]
return "overview"
} else if(myRequest.getQuery().equalsIgnoreCase("detail")) {
[...]
return "detail"
}
}
Since both methods are unmarshalling to the same object, you don't really need two separate methods/mappings.
I'm working on converting a legacy project to Spring (trying to adjust little as possible for now) and I'm running into a small issue with mapping/translating legacy parameters to a model attribute object. I may be completely wrong in thinking about this problem but it appears to me that to translate a parameter to a specific model attribute setter is to pass in the request parameter through a method for creating a model attribute and manually call the correct setter:
#ModelAttribute("form")
public MyForm createMyForm(#RequestParameter("legacy-param") legacy) {
MyForm myForm = new MyForm();
myForm.setNewParam(legacy);
return myForm;
}
I don't necessarily want to change the request parameter name yet since some javascript and JSPs are depending on it being named that way but is there any way to do something like this? Or is there a different way to map/translate request parameters to model attributes?
public class MyForm {
#ParameterName("legacy-param")
private String newParam;
public void setNewParam(String value) { ... }
public String getNewParam() { ... }
}
#Controller
public class MyController {
#RequestMapping("/a/url")
public String myMethod(#ModelAttribute("form") MyForm myForm, BindingResult result) { ... }
}
The way you've written that model attribute method is indeed odd. I'm not entirely clear what you're actually trying to do.Assuming there are many parameters, you're going to end up with an awful lot of instances of MyForm in your ModelMap. A more 'normal' way to create model attribute would be like this:
#ModelAttribute("legacyParamNotCamel")
public MyForm createMyForm(#RequestParameter("legacy-param-not-camel") String legacy) {
return legacy;
}
Then in the JSP you can refer to it directly in expression language. e.g.,
<c:out value="${legacyParamNotCamel}"/>
If you want to put them onto a form backing object, you need to do it all in a single method that creates the object, not make new copies of it in each method. (assuming your form has more than a single parameter associated with it.)
--
It seems like what you're really trying to do though is translate the parameter names in the request before the web data binder gets ahold of it, so that you can bind oddly named parameters onto a java bean? For that you'll need to use an interceptor that translates the names before the binding process begins, or make your own subclass of the databinder than can take a property name translation map.
You placed the #ModelAttribute at the Method Level but the intention seems to be more of a formBackingObject hence we should be dealing at the Method Parameter Level
There's a difference.
I put up an explanation here on my blog along examples at Spring 3 MVC: Using #ModelAttribute in Your JSPs at http://krams915.blogspot.com/2010/12/spring-3-mvc-using-modelattribute-in.html