What is the best way to achieve a common REST request body unwrap effect with Spring MVC #RestController ?
In other words, assuming I have the following controller:
#RestController
public class MyController {
#RequestMapping(value = "/", method = POST)
public Object hello(#RequestBody MyDTO dto) {
...
}
}
I would like the actual post body to be:
{
"version": "1.0",
"payload": {
...
}
}
Which can be represented by the following class:
public class ApiRequestDTO<TPayload> {
private String version;
private TPayload payload;
...
// Getters and Setters...
...
}
So in this particular case the client would send an instance of ApiRequestDTO<MyDTO>.
I achieved the opposite (response body wrapper) using a #ControllerAdvice, but I notice that it won't work exactly for the request body. One possible approach I can see is to decorate all the relevant message converters. But I was wondering if there is a better approach?
Related
#ControllerAdvice
public class CA implements ResponseBodyAdvice<Object> { }
This class will wrap the response
#GetMapping
public Person getPerson()
{
return new Person();
}
In Swagger I want it to show WrappedResponse but instead it shows response of return type here(Person).
What changes to make so that it will show the wrapped response ?
Tried different combinations of APIReponse, APIModel, APIResponses of swagger but nothing seems to work
Problem statement - Path-params sent from RestAssured test case received as null.
An API definition like,
#RequestMapping("myapp")
public interface MyApi {
#GetMapping(path = "getItem/{path1}/{path2}/{path3}")
public ResponseEntity<MyResponse> getItem(#PathVariable("path1") String path1,
#PathVariable("path2") String path2, #PathVariable("path3") String path3);
}
#RestController
public class MyController implements MyApi {
public ResponseEntity<MyResponse> getItem(String path1, String path2, String path3) {
return myService.delegateToMethod(path1, path2, path3);
}
}
This setup fine and gives result.Functional-test code described in shortPart of Test class code
#Test
public void getItemTest() {
MyResponse response = apiStub.callGetItem("1","2","3");
Assert.notNull(response);
}
Part of Stub class
public MyResponse callGetItem(String param1, String param2, String param3) {
return given().port(servicePort)
.pathParam("path1", param1)
.pathParam("path2", param2)
.pathParam("path3", param3)
.when().get("/myapp/getItem/{path1}/{path2}/{path3}")
.then().statusCode(200)
.extract()
.as(MyResponse.class);
}
Observation Test assertion fails. By putting debug point in controller class, I see path-values are being received as nullWhat's Interesting If I put API definition directly in controller, it works !!DISCLAIMER Code is kept short for brevity, shall expand on sections as requested.Request If duplicate question or bug, request re-direction.
Try to store your params values in the Map object and pass it to .pathParams(Map map) method instead of invoking the same method multiple times.
You need to put #RestController on API definition as well as the implementing controller.
#RestController
#RequestMapping("myapp")
public interface MyApi {
#GetMapping(path = "getItem/{path1}/{path2}/{path3}")
public ResponseEntity<MyResponse> getItem(#PathVariable("path1") String path1,
#PathVariable("path2") String path2, #PathVariable("path3") String path3);
}
UPDATE Root-Cause was spring-boot version. I was using old version(2.0.8.RELEASE). Segregation of API and Controller, as described does not work. Such end-point results as,
http://localhost:8080/myapp/getItem/{path1}/{path2}/{path3}?path1=v1&path2=v2&path3=v3
Same code, upgraded to spring-boot version 2.4.5, worked correctly.
I have 2 messages like this...
{
"type":"MessageString",
"message": {
"value":"..."
}
}
and
{
"type":"MessageObject",
"message": {
"value": { ... }
}
}
Then I have a Spring Boot endpoint like...
#Controller
#RequestMapping("...")
public class MyController {
#PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE
)
#ResponseBody
public String processMessage(
#RequestBody (?) wrapper
){
...
}
}
I would like to know what kind of classes I can use to accept both types? I had it working like this....
class MessageWrapper<T extends MessageType>{
private T message;
}
But then I still have to declare the type in the response body like...
#RequestBody MessageWrapper<Object> wrapper
or
#RequestBody MessageWrapper<String> wrapper
So this wouldn't work because I can't have 2 response bodies.
I am trying to keep this as simple as possible so I would like to avoid making the #RequestBody a string then parsing it inside the controller.
I have a bunch of params in my controller and want to map all of them to a separate POJO to keep readability. There is also a #CookieValue, #RequestHeader I need to evaluate and aim for a solution to also map them to that POJO. But how?
I saw a possible solution on a blog but it doesn't work, the variable stays null.
Controller:
#RequestMapping(path = MAPPING_LANGUAGE + "/category", produces = MediaType.TEXT_HTML_VALUE)
#ResponseBody
public String category(CategoryViewResolveModel model) {
doSomething();
}
And my POJO is this:
public class CategoryViewResolveModel {
private String pageLayoutCookieValue;
public CategoryViewResolveModel() {
}
public CategoryViewResolveModel(
#CookieValue(value = "SOME_COOKIE", required = false) String pageLayoutCookieValue) {
this.pageLayoutCookieValue = pageLayoutCookieValue;
}
... some other RequestParams, PathVariables etc.
}
According to the documentation it's not possible for #CookieValue and #RequestHeader.
This annotation is supported for annotated handler methods in Servlet
and Portlet environments.
Take a look at:
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-creating-a-custom-handlermethodargumentresolver/
instead of using getParameter to access request parameters you can use getHeader to retrieve the header value and so define your CategoryViewResolveModel just as you were requesting
This works fine
In my Spring-based application, I have set up a HTTP-based REST endpoint. This endpoint "speaks" JSON:
#Controller
public class HttpRestController implements RestController {
#Override
#RequestMapping(value = "/users/{user}", method = RequestMethod.GET)
#ResponseBody
public getUser(#PathVariable User user) {
User jsonFriendlyUser = new JacksonAnnotatedUser(user);
return jsonFriendlyUser;
}
}
As these JSON payloads have to follow unusual naming conventions, I used annotations such as #JsonRootName and #JsonProperty to customize the serialized property names:
#JsonRootName("uussaaar")
public class JacksonAnnotatedUser implements User {
//...
public int getId() {
return id;
}
#JsonProperty("naammee")
public String getName() {
return name;
}
#JsonSerialize(using = FriendsJsonSerializer.class )
public Set<User> getFriends() {
return friends;
}
#JsonIgnore
public String getUnimportantProperty() {
return unimportantProperty;
}
}
With this custom JSON metadata, querying /users/123 via HTTP returns the following JSON payload:
{"uussaaar":
{
"id":123,
"naammee":"Charlie",
"friends": [456, 789]
}
}
The following doesn't work as expected
Now I am playing around with Spring's WebSocket support: I want to create a STOMP-based REST endpoint. Therefore i created a StompRestController like this:
#Controller
public class StompRestController implements RestController {
#Override
#SubscribeMapping("/users/{user}")
public getUser(#DestinationVariable User user) { // assuming this conversion works
User jsonFriendlyUser = new JacksonAnnotatedUser(user);
return jsonFriendlyUser;
}
I would have expected for #SubscribeMapping/#MessageMapping to follow the same JSON serialization behavior as #RequestMapping. But this is not the case. Instead, when querying this WebSocket/STOMP endpoint, #SubscribeMapping/#MessageMapping-annotated methods will result in sending a STOMP message to clients with a payload/body corresponding to the "normal" Jackson serialization rules, e.g.
{
"id":123,
"name":"Charlie"
"friends":[{argh recursion}, ...],
"unimportantProperty":"This property shall not be part of JSON serialization"
}
Therefore:
How can I have #SubscribeMapping/#MessageMapping-annotated methods obey custom #JsonXXX annotations for returned values?
Is there another way aside #JsonXXXfor doing such returned value serialization?