Spring REST #RequestBody consume (XML or JSON) to POJO without annotations - spring

I am writing a Springboot REST endpoint and want to consume XML or JSON requests for a simple service. In either case I want Spring to construct an #RequestBody pojo WITHOUT annotating any of the POJO. Is this OK? Safe? Performant?
I was reading this which told me about configuration by exception. To me this means if I structure my request to contain the exact name and case as the POJO member variables I want to populate the #RequestBody will be able to create my class SomeRequest.
If this is my REST endpoint:
#RequestMapping(value = GET_FOR_SOMETHING, method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE},,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
StatusResponse<Boolean> getMpdForReqest(#RequestBody SomeRequest request)
And this is my POJO:
public class SomeRequest {
String one;
String two;
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
}
My JSON request:
{
"one": "str",
"two": "str"
}
My XML request:
<SomeRequest>
<one>str</one>
<two>str</two>
</SomeRequest>
My question is: why should I not do this or is it perfectly fine?
Thank you all.

TLDR; It is perfectly fine.
Is this OK? Safe? Performant?
Yes, it is as performant as it's annotated cousin, if you take program efficiency into account.
If you take the Programmer efficiency into account, it is much more efficient as the developer doesn't have to deal with a bunch of annotations.
Speaking of Programmer efficiency, I would encourage you to use project Lombok instead of crapping your POJO with bunch of getter and setter methods, that's what cool kids do now a days.
Catch
This will work fine as long as your json fields are one word and small case.
When you have multi-word field name, Java standard is the camelCase and usually JSON standard is the snake_case. In this case, you can either have a Class level Annotation (one per class, so not much ugly). Or, since you are using spring boot, you can use an application wide property (spring.jackson.property-naming-strategy = SNAKE_CASE ).
If you have weird json field names with spaces in between, you might need to use #JsonProperty annotation. Remember, this is a perfectly valid json
{
"just a name with a space" : "123"
}

POJO as RequestBody works perfectly fine. Just note that Spring however will return 400 - Bad Request for every request that can not be mapped to the #RequestBody annoted object.

Related

Spring WebFluxTest weird behaviour when using Flux<String> as response

I'm currently facing a weird bahaviour when testing the following controller using Spring WebFlux (v. 5.2.6)
#RestController
#RequestMapping(path = "/address", produces = MediaType.APPLICATION_JSON_VALUE)
public class AddressController {
#GetMapping(path = "/postcode")
public Flux<String> listPostCodes(...) {
return Flux.just("4711", "4712");
}
#GetMapping(path = "/cities")
public Flux<City> listCities() {
return Flux.just(new City("foo"), new City("bar"));
}
}
This controller is embedded within a "hello world" Spring-Boot application using spring-webflux-starter and a simple main class. Class City has just one property "name".
Now I have the following test (Junit5) to ensure response from the above mentioned controller
#SpringWebFluxTest
public AddressControllerTest {
#Test
public void postcodes() {
webTestClient.get()
.uri("/address/postcode")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$")
.isArray()
.jsonPath("$[0]")
.isEqualTo("4711")
}
#Test
public void cities() {
webTestClient.get()
.uri("/address/cities")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$")
.isArray()
.jsonPath("$[0].name")
.isEqualTo("foo")
}
}
You'd expected both test to pass? Me too. Unfortunately the first fails, telling me that the response body root isn't a json array, but a Long:
Expected: an instance of java.util.List
but: <47114712L> is a java.lang.Long
Why's that? Both responses are Flux, so I'd expect both response bodies to be an array, but only if the elements aren't "simple" types this seems to work in test. If I use postman to assert that behavior everything works exactly as expected, so I'd assume a testing problem somehow.
Could somebody explain that to me or might have a solution to this?
Thanks in advance
It's not an issue with the tests, the actual behaviour isn't what you expect.
Both responses are Flux, so I'd expect both response bodies to be an array
A Flux is not analogous to a list. It's a stream of data, which can be output verbatim, or collected into some other data structure when it's complete (a list being one example.)
In this case of course, the content type specified indicates you want that Flux to be collected into a list where possible - but this isn't universally the case. With POJOs, collections & arrays, Jackson will serialise them, and output an array. But here we're using raw strings, not using a JSON serialiser for those strings, so it's just concatenating & outputting them raw as they appear. (The concatenated postcodes just so happen to be all digits of course, hence why you then get an error about a long value.)
If you want a JSON array, you'll need to use:
#GetMapping(path = "/postcode")
public Mono<List<String>> listPostCodes() {
return Flux.just("4711", "4712").collectList();
}
To dispel a common myth with this approach ahead of time - you're not blocking by using collectList(), and you're not losing anything underneath the covers (since even if you use a Flux, the framework will still need to collect to a list internally to return the JSON array.)

How to restrict JSON payload from containing additional fields with Spring?

I have a basic User DTO class...
public class User {
#JsonProperty("firstName")
private String firstName;
#JsonProperty("lastName")
private String lastName;
}
...and a basic request handler in a #RestController class:
#RequestMapping(path = "/users", method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE })
public UserMessage createUser(#RequestBody User user){
return userService.createUser(user);
}
How can I restrict incoming JSON payloads to contain at most only the required keys?
i.e. accept this payload:
{
"firstName":"foo",
"lastName":"bar"
}
And throw a custom exception on this:
{
"firstName":"foo",
"lastName":"bar",
"type":"admin",
"asdf":"asdf"
}
I read about custom Converters, ArgumentResolvers, and I believe I could simply put an additional Map parameter in the handler and validate before service call, however I'd like to know the "best" way of handling this issue.
Regarding the User bean in your example it also already not possible, that potential other JSON fields than firstName and lastName could be mapped, simply because there are no fields in User which could hold the relevant data.
Should the User bean in your question be not complete, e.g. for simplicity reasons, and contain more fields, also then should everything be fine, as long as you did not configure your your ObjectMapper with com.fasterxml.jackson.databind.DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES => false or you use the annotation #JsonIgnoreProperties(ignoreUnknown = true) on your bean.
To sum it up: Jackson's default behavior is FAIL_ON_UNKNOWN_PROPERTIES (default: true)
For further information you can also consult the respective Deserialization docs.
Solved the issue, this thread helped
#JsonIgnoreProperties(ignoreUnknown=false) is not working in Spring 4.2.0 and upper version
mle, your answer wasn't right, since I was using the latest version of Spring Framework and the ObjectMapper's FAIL_ON_UNKNOWN_PROPERTIES is turned off by default. Additionally I was needed to set #JsonIgnoreProperties(ignoreUnknown = false) in my User DTO class (as the actual class' superclass had this set to true).
Tested it, runs like a charm, while custom errors can be handled in a #ExceptionHandler(HttpMessageNotReadableException.class) annotated handler.

How to auto generate response fields that do not have POJO

We have a service that simply returns the json document on a GET request. Since we do not have the POJO for the response "model", it appears we won't be able to use the auto response fields generation "goodness".
One option for us is to create the Pojos (quite large, about 50 attributes) and a corresponding controller that uses the pojos. This is awkward as we now have to maintain the model and corresponding controller just so we can auto generate the model.
Any ideas on how we can still leverage some auto generation of the response fields would be greatly appreciated.
Here's the controller I'm referring to:
#RestController
#RequestMapping("/api")
public class ProductController {
#Autowired
ProductService productService;
#RequestMapping(value = { "/products/{ids}" }, method = { RequestMethod.GET },
produces = "application/json", headers={"accept=application/json"})
#Timed
#ExceptionMetered
#LogExecutionTime
public String getProductDetails(#PathVariable("id") String id) {
return productService.getProductDetails(id);
}
At the moment I see no way of leveraging the auto generation without putting additional effort into it. Spring Auto REST Docs works by inspecting POJOs with a Jackson visitor (static introspection without runtime information) and there is currently no way of deriving the JSON fields from a string (would be dynamic at runtime). Thus, I only see two options:
The approach that you already described: Creating the corresponding POJO and using it.
Using Spring REST Docs for the corresponding test and manually document each field in the test. Might be the better option here if you do not want to alter the production code.

Kotlin Spring Boot form-urlencoded POST requests with a Map

I've just started playing around with Kotlin and Spring Boot and decided to write a simple endpoint that takes a form-urlencoded POST request. I didn't feel like writing an actual data class for the body, so I tried to just use a Map for the body, hoping I could just access key/value pairs. I first tried:
#RestController
class MyController {
#RequestMapping(value = "/endpoint", method = arrayOf(RequestMethod.POST),
consumes = arrayOf("application/x-www-form-urlencoded"))
fun myEndpoint(#RequestBody body: Map<String,String>): String {
// Do stuff
}
}
But that resulted in a 415 error about unsupported media type...which I read was due to the use of #RequestBody and form-urlencoded POSTs. I subsequently tried using the #ModelAttribute instead but then received
Failed to instantiate [java.util.Map]: Specified class is an interface
Hardly surprising as I was completely hacking. I also tried without any annotations for the body, but then none of the form parameters were injected. I know I could add a data class to solve this, but I'd like to know if this can be done generically using a Map as I've done a similar thing in Java before.
Thanks.
You need to annotate your body param with #RequestParam
#RequestMapping(value = "/endpoint", method = [(RequestMethod.POST)],
consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
fun myEndpoint(#RequestParam body: Map<String,String>): String {
return //...
}
(Also note that I removed the arrayOf calls in favour of array literals, which are available for annotations as of Kotlin 1.2)

Conditionally returning both JSON and (HTML) templates from #RestController in Spring Boot

Most of the similar questions seem to have the opposite problem I’m having.
I’m building a Spring Boot-based webapp using #RestController. The JSON responses work well, but now I want to support returning HTML via templates (Thymeleaf, specifically). All the examples show building methods like this:
#RequestMapping(method = RequestMethod.GET)
String index()
{
return "index";
}
And that works fine, so long as the class it’s in is annotated with #Controller. If I annotate with #RestController, I get the literal string "index" back. This makes sense, since #RestController implies #ResponseBody.
I have a few questions about this in general…
Is the right thing to do to use #Controller and explicit #ResponseBody annotations on the methods designed to return JSON?
I worry that my Controller classes will grow quite large, as I’ll have two implementations for most of the GET methods (one to return the HATEOAS JSON, one to return HTML with a lot more stuff in the model). Are there recommended practices for factoring this?
Advice is appreciated. Thanks!
Is the right thing to do to use #Controller and explicit #ResponseBody annotations on the methods designed to return JSON?
It is as long as your controllers are small and contain only few methods.
I worry that my Controller classes will grow quite large, as I’ll have two implementations for most of the GET methods (one to return the HATEOAS JSON, one to return HTML with a lot more stuff in the model). Are there recommended practices for factoring this?
If they grow and become hard to read split into one #Controller returning HTML pages and #RestController returning JSON.
To summarise, focus on readability. Technically both approaches are correct.
#RestController
public class SampleController {
#GetMapping(value = "/data/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public CustomClass get(#PathVariable String id) {
return newsService.getById(id);
}
#GetMapping(value = "/data/{id}", produces = MediaType.TEXT_HTML_VALUE)
public String getHTML(#PathVariable String id) {
return "HTML";
}
}
Instead of a String you can return a View or a ModelAndView:
#RequestMapping(method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
ModelAndView index()
{
return new ModelAndView("index");
}
That allows you to return HTML from controllers annotated with #RestController.

Resources