Reputation: 16831
Using Spring Boot I've build a toy REST service with a single resource like so:
@RestController
@RequestMapping("/checkout")
public class CheckoutRestController {
@PostMapping("/book")
public boolean buyBook(@RequestParam CreditCard creditCard, @RequestParam ShippingAddress shippingAddress) {
return true;
}
}
Both CreditCard
and ShippingAddress
are POJOs (plain old Java objects) that I've coded up).
I've tried posting to this endpoint with this payload:
{
"creditCard" : {
"nameOnCard":"foo",
"number":12345678,
"expiryMonth":12,
"expiryYear":2018,
"ccv":100
},
"shippingAddress" : {
"steert":"foor",
"houseNumber":"1a",
"city":"bar",
"state":"bazz",
"country":"buzz",
"zipCode":"booz"
},
}
But I get an error:
{
"timestamp": "2018-03-13T11:36:52.745+0000",
"status": 400,
"error": "Bad Request",
"message": "Required CreditCard parameter 'creditCard' is not present",
"path": "/checkout/book"
}
I know a workaround would be to wrap both POJOs in a request wrapper but I'd rather not unless I really have to.
Is it possible to post two @RequestParam annotated objects? If so, what would the JSON look like?
Upvotes: 5
Views: 21359
Reputation: 3634
@RequestParam
parameters are query parameters, not body parameters.
This means that your method:
@PostMapping("/book")
public boolean buyBook(@RequestParam CreditCard creditCard, @RequestParam
ShippingAddress shippingAddress) {
return true;
}
Expects the following request:
POST /checkout/book?creditCard=<...>&shippingAddress=<...>
However, Spring doesn't know how to convert a String
query parameter to CreditCard
or to ShippingAddress
.
You might solve this by implementing a Converter
, as follows:
public class StringToCreditCardConverter implements Converter<String, CreditCard> {
@Override
public CreditCard convert(String source) {
<...>
}
}
However, I do not recommend this as it is not the standard for request bodies and would cause quite a lot of confusion and maintainability issues.
Instead, the recommended way is as follows:
@PostMapping("/book")
public boolean buyBook(@RequestBody BookCreationRequest bookCreationRequest) {
CreditCard creditCard = bookCreationRequest.getCreditCard();
ShippingAddress shippingAddress = bookCreationRequest.getShippingAddress();
...
}
With the bookCreationRequest
containing the CreditCard
and ShippingAddress
fields (note: you could use e.g. lombok
to reduce the boilerplate code):
public class BookCreationRequest {
private ShippingAddress shippingAddress;
private CreditCredit creditCard;
public ShippingAddress getShippingAddress() {...}
public CreditCard getCreditCard() {...}
public BookCreationRequest(ShippingAddress shippingAddress, CreditCard creditCard) {
this.creditCard = creditCard;
this.shippingAddress = shippingAddress;
}
Which would then expect a request JSON as follows:
POST /checkout/book
Payload:
{
"creditCard": {
...
},
"shippingAddress": {
...
}
}
Note that there can only be a single @RequestBody
parameter in a request.
Upvotes: 19
Reputation: 31443
Yes - create a wrapper Request object. It's not a workaround - it's actually the recommended approach.
You see in your json you have:
{ //wrapper
{}, //first complex data object / mapped to pojo
{} //second complex data object / mapped to pojo
}
1) You can easily apply validation to your Request object with @Valid
and hence validate both POJOs.
2) You don't have to worry about the order in the payload.
3) You're using @RequestParam
incorrectly. @RequestBody
is what maps the whole POST payload. You have to remove the @RequestParam
annotation and use @RequestBody
instead.
Upvotes: 5