Related
Is it necessary to wrap in a backing object? I want to do this:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody String str1, #RequestBody String str2) {}
And use a JSON like this:
{
"str1": "test one",
"str2": "two test"
}
But instead I have to use:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Holder holder) {}
And then use this JSON:
{
"holder": {
"str1": "test one",
"str2": "two test"
}
}
Is that correct? My other option would be to change the RequestMethod to GET and use #RequestParam in query string or use #PathVariable with either RequestMethod.
While it's true that #RequestBody must map to a single object, that object can be a Map, so this gets you a good way to what you are attempting to achieve (no need to write a one off backing object):
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Map<String, String> json) {
//json.get("str1") == "test one"
}
You can also bind to Jackson's ObjectNode if you want a full JSON tree:
public boolean getTest(#RequestBody ObjectNode json) {
//json.get("str1").asText() == "test one"
You are correct, #RequestBody annotated parameter is expected to hold the entire body of the request and bind to one object, so you essentially will have to go with your options.
If you absolutely want your approach, there is a custom implementation that you can do though:
Say this is your json:
{
"str1": "test one",
"str2": "two test"
}
and you want to bind it to the two params here:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
public boolean getTest(String str1, String str2)
First define a custom annotation, say #JsonArg, with the JSON path like path to the information that you want:
public boolean getTest(#JsonArg("/str1") String str1, #JsonArg("/str2") String str2)
Now write a Custom HandlerMethodArgumentResolver which uses the JsonPath defined above to resolve the actual argument:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.jayway.jsonpath.JsonPath;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String body = getRequestBody(webRequest);
String val = JsonPath.read(body, parameter.getMethodAnnotation(JsonArg.class).value());
return val;
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) servletRequest.getAttribute(JSONBODYATTRIBUTE);
if (jsonBody==null){
try {
String body = IOUtils.toString(servletRequest.getInputStream());
servletRequest.setAttribute(JSONBODYATTRIBUTE, body);
return body;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return "";
}
}
Now just register this with Spring MVC. A bit involved, but this should work cleanly.
For passing multiple object, params, variable and so on. You can do it dynamically using ObjectNode from jackson library as your param. You can do it like this way:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjectNode objectNode) {
// And then you can call parameters from objectNode
String strOne = objectNode.get("str1").asText();
String strTwo = objectNode.get("str2").asText();
// When you using ObjectNode, you can pas other data such as:
// instance object, array list, nested object, etc.
}
I hope this help.
You can mix up the post argument by using body and path variable for simpler data types:
#RequestMapping(value = "new-trade/portfolio/{portfolioId}", method = RequestMethod.POST)
public ResponseEntity<List<String>> newTrade(#RequestBody Trade trade, #PathVariable long portfolioId) {
...
}
The easy solution is to create a payload class that has the str1 and the str2 as attributes:
#Getter
#Setter
public class ObjHolder{
String str1;
String str2;
}
And after you can pass
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjHolder Str) {}
and the body of your request is:
{
"str1": "test one",
"str2": "two test"
}
#RequestParam is the HTTP GET or POST parameter sent by client, request mapping is a segment of URL which's variable:
http:/host/form_edit?param1=val1¶m2=val2
var1 & var2 are request params.
http:/host/form/{params}
{params} is a request mapping. you could call your service like : http:/host/form/user or http:/host/form/firm
where firm & user are used as Pathvariable.
Instead of using json, you can do simple thing.
$.post("${pageContext.servletContext.contextPath}/Test",
{
"str1": "test one",
"str2": "two test",
<other form data>
},
function(j)
{
<j is the string you will return from the controller function.>
});
Now in the controller you need to map the ajax request as below:
#RequestMapping(value="/Test", method=RequestMethod.POST)
#ResponseBody
public String calculateTestData(#RequestParam("str1") String str1, #RequestParam("str2") String str2, HttpServletRequest request, HttpServletResponse response){
<perform the task here and return the String result.>
return "xyz";
}
Hope this helps you.
I have adapted the solution of Biju:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
private ObjectMapper om = new ObjectMapper();
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String jsonBody = getRequestBody(webRequest);
JsonNode rootNode = om.readTree(jsonBody);
JsonNode node = rootNode.path(parameter.getParameterName());
return om.readValue(node.toString(), parameter.getParameterType());
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) webRequest.getAttribute(JSONBODYATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
if (jsonBody==null){
try {
jsonBody = IOUtils.toString(servletRequest.getInputStream());
webRequest.setAttribute(JSONBODYATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return jsonBody;
}
}
What's the different:
I'm using Jackson to convert json
I don't need a value in the annotation, you can read the name of the
parameter out of the MethodParameter
I also read the type of the parameter out of the Methodparameter => so the solution should be generic (i tested it with string and DTOs)
BR
Not sure where you add the json but if i do it like this with angular it works without the requestBody:
angluar:
const params: HttpParams = new HttpParams().set('str1','val1').set('str2', ;val2;);
return this.http.post<any>( this.urlMatch, params , { observe: 'response' } );
java:
#PostMapping(URL_MATCH)
public ResponseEntity<Void> match(Long str1, Long str2) {
log.debug("found: {} and {}", str1, str2);
}
You can also use a MultiValue Map to hold the requestBody in.
here is the example for it.
foosId -> pathVariable
user -> extracted from the Map of request Body
unlike the #RequestBody annotation when using a Map to hold the request body we need to annotate with #RequestParam
and send the user in the Json RequestBody
#RequestMapping(value = "v1/test/foos/{foosId}", method = RequestMethod.POST, headers = "Accept=application"
+ "/json",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE ,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public String postFoos(#PathVariable final Map<String, String> pathParam,
#RequestParam final MultiValueMap<String, String> requestBody) {
return "Post some Foos " + pathParam.get("foosId") + " " + requestBody.get("user");
}
Use an inner class
#RestController
public class MyController {
#PutMapping("/do-thing")
public void updateFindings(#RequestBody Bodies.DoThing body) {
...
}
private static class Bodies {
public static class DoThing {
public String name;
public List<String> listOfThings;
}
}
}
request parameter exist for both GET and POST ,For Get it will get appended as query string to URL but for POST it is within Request Body
Good.
I suggest creating a Value Object (Vo) that contains the fields you need. The code is simpler, we do not change the functioning of Jackson and it is even easier to understand.
Regards!
You can achieve what you want by using #RequestParam. For this you should do the following:
Declare the RequestParams parameters that represent your objects and set the required option to false if you want to be able to send a null value.
On the frontend, stringify the objects that you want to send and include them as request parameters.
On the backend turn the JSON strings back into the objects they represent using Jackson ObjectMapper or something like that, and voila!
I know, its a bit of a hack but it works! ;)
you can also user #RequestBody Map<String, String> params,then use params.get("key") to get the value of parameter
If somebody is interested in the webflux solution, below is a reactive version, based on Biju answer.
Please note that there is one very small but synchronized chunk, needed to protect the body from being consumed more than once. If you prefer a fully non-blocking version, I suggest publishing the flux that obtains json on the same scheduler, to make checking and reading sequential.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
#Slf4j
#RequiredArgsConstructor
public class JsonArgumentResolver implements HandlerMethodArgumentResolver {
private static final String ATTRIBUTE_KEY = "BODY_TOSTRING_RESOLVER";
private final ObjectMapper objectMapper;
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArgument.class);
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
String fieldName = parameter.getParameterName();
Class<?> clz = parameter.getParameterType();
return getRequestBody(exchange).map(body -> {
try {
JsonNode jsonNode = objectMapper.readTree(body).get(fieldName);
String s = jsonNode.toString();
return objectMapper.readValue(s, clz);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
});
}
private Mono<String> getRequestBody(ServerWebExchange exchange) {
Mono<String> bodyReceiver;
synchronized (exchange) {
bodyReceiver = exchange.getAttribute(ATTRIBUTE_KEY);
if (bodyReceiver == null) {
bodyReceiver = exchange.getRequest().getBody()
.map(this::convertToString)
.single()
.cache();
exchange.getAttributes().put(ATTRIBUTE_KEY, bodyReceiver);
}
}
return bodyReceiver;
}
private String convertToString(DataBuffer dataBuffer) {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
}
}
I'd like to mock up some JSON (that I'm reading from a file), and return it as a result of some Spring Controller.
File contains of course correct JSON data format inside, like:
{"country":"","city":""...}
My controller looks like:
#RestController
#RequestMapping("/test")
public class TestController {
#Value("classpath:/META-INF/json/test.json")
private Resource testMockup;
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody JSONObject getTest() throws IOException {
JSONObject jsonObject = new JSONObject(FileUtils.readFileToString(testMockup.getFile(), CharEncoding.UTF_8));
return jsonObject;
}
}
There is no issue with reading the file itself etc.
jsonObject itself, is correct from debbuging PoV, however I'm getting HTTP Status 406 from the browser.
I've tried also just returning String (by returning jsonObject.toString()), instead of JSONObject.
However it causes encoding issue - so that JSON from the browser, is not the JSON itself (some additional slashes, quotation marks etc.).
Is there any way, to return JSON from file?
This is what worked for me.
Java:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
#RestController("/beers")
public class BeersController {
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody
Object getBeers() {
Resource resource = new ClassPathResource("/static/json/beers.json");
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(resource.getInputStream(), Object.class);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Kotlin:
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.core.io.ClassPathResource
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
#RestController
#RequestMapping("/beers")
class BeersController {
#GetMapping
fun getBeers(): Any? {
val resource = ClassPathResource("/static/json/beers.json")
return ObjectMapper().readValue(resource.inputStream, Any::class.java)
}
}
#Controller
public class TestController {
#RequestMapping(
value = "/test",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE
)
String getTest() {
return "json/test.json";
}
}
This worked for me.
Path to JSON file: \src\main\resources\static\json\test.json
That't not valid JSON. If it's not a typo, try reformatting your file to look like
{"country":"","city":""}
Note the opening quote around the property name.
Have you tried with jackson?
ObjectMapper mapper = new ObjectMapper();
Object json = mapper.readValue(input, Object.class);
String s = mapper.writeValueAsString(json);
And perhaps writing it straight to response body? Jackson should take care of the json.
I know I have been very late for this but why don't you try the below workaround.
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody String getTest() throws IOException {
JSONObject jsonObject = new JSONObject(FileUtils.readFileToString(testMockup.getFile(), CharEncoding.UTF_8));
return jsonObject.toString();
}
I have a controller class that looks like:
#RequestMapping(value="/enter/two-factor/blacklist", method = RequestMethod.GET, produces = "application/json")
public #ResponseBody String getBlacklist(int days) {
List<Honey> honey = honeyService.fetchAllPings(days);
List<Locate> locations = honeyService.parseDistinctLocations(honey);
return GeneralUtil.convertToJson(locations);
}
The 'GeneralUtil.convertToJson()' method returns a pretty-print string with this code:
public static String convertToJson(List<Locate> locations){
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = "";
try {
json = gson.toJson(new Blacklist(locations));
} catch (Exception e){
e.printStackTrace();
}
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(json);
String prettyJsonString = gson.toJson(je);
System.out.println(prettyJsonString);
return prettyJsonString;
}
However, when the page renders, the JSON is not pretty-printed. What am I missing?
You're overlooking the fact that #ResponseBody with a String return type will cause a StringHttpMessageConverter to write the value returned to the HTTP response body. This HttpMessageConverter will produce a content-type of text/plain. I believe browsers don't render new line characters or trim whitespace between them. Your pretty printing gets lost.
What you should do is register your own GsonHttpMessageConverter or MappingJackson2HttpMessageConverter which is set up to do pretty printing. Then change your handler method's return type to List<Locate> and return locations directly. These HttpMessageConverter implementations produce application/json which browsers should render literally.
(Gson and ObjectMapper instances are thread safe. There's absolutely no reason to reinstantiate those classes for every request.)
Spring throws an error when I send json array. I am not sure what I am missing here.
RequestBody
{
"deliverySessionId":"c1fb327b-98a8-46d4-9e82-ce7507b5be93",
imageNames: ["name1", "name2"]
}
Endpoint
#RequestMapping(value = { "/examImages/" }, method = { RequestMethod.POST } )
public #ResponseBody ImageResponseCommand streamExamImages( #RequestBody ImageResponseCommand imageResponseCommand ) {
Error
The request sent by the client was syntactically incorrect.
It works fine if my request doesn't contain imageNames property.
{ "deliverySessionId":"c1fb327b-98a8-46d4-9e82-ce7507b5be93" }
Your JSON string isn't formatted properly. Object key's need to be wrapped in quotes.
{
"deliverySessionId":"c1fb327b-98a8-46d4-9e82-ce7507b5be93",
"imageNames": ["name1", "name2"]
}
Is it necessary to wrap in a backing object? I want to do this:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody String str1, #RequestBody String str2) {}
And use a JSON like this:
{
"str1": "test one",
"str2": "two test"
}
But instead I have to use:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Holder holder) {}
And then use this JSON:
{
"holder": {
"str1": "test one",
"str2": "two test"
}
}
Is that correct? My other option would be to change the RequestMethod to GET and use #RequestParam in query string or use #PathVariable with either RequestMethod.
While it's true that #RequestBody must map to a single object, that object can be a Map, so this gets you a good way to what you are attempting to achieve (no need to write a one off backing object):
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Map<String, String> json) {
//json.get("str1") == "test one"
}
You can also bind to Jackson's ObjectNode if you want a full JSON tree:
public boolean getTest(#RequestBody ObjectNode json) {
//json.get("str1").asText() == "test one"
You are correct, #RequestBody annotated parameter is expected to hold the entire body of the request and bind to one object, so you essentially will have to go with your options.
If you absolutely want your approach, there is a custom implementation that you can do though:
Say this is your json:
{
"str1": "test one",
"str2": "two test"
}
and you want to bind it to the two params here:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
public boolean getTest(String str1, String str2)
First define a custom annotation, say #JsonArg, with the JSON path like path to the information that you want:
public boolean getTest(#JsonArg("/str1") String str1, #JsonArg("/str2") String str2)
Now write a Custom HandlerMethodArgumentResolver which uses the JsonPath defined above to resolve the actual argument:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.jayway.jsonpath.JsonPath;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String body = getRequestBody(webRequest);
String val = JsonPath.read(body, parameter.getMethodAnnotation(JsonArg.class).value());
return val;
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) servletRequest.getAttribute(JSONBODYATTRIBUTE);
if (jsonBody==null){
try {
String body = IOUtils.toString(servletRequest.getInputStream());
servletRequest.setAttribute(JSONBODYATTRIBUTE, body);
return body;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return "";
}
}
Now just register this with Spring MVC. A bit involved, but this should work cleanly.
For passing multiple object, params, variable and so on. You can do it dynamically using ObjectNode from jackson library as your param. You can do it like this way:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjectNode objectNode) {
// And then you can call parameters from objectNode
String strOne = objectNode.get("str1").asText();
String strTwo = objectNode.get("str2").asText();
// When you using ObjectNode, you can pas other data such as:
// instance object, array list, nested object, etc.
}
I hope this help.
You can mix up the post argument by using body and path variable for simpler data types:
#RequestMapping(value = "new-trade/portfolio/{portfolioId}", method = RequestMethod.POST)
public ResponseEntity<List<String>> newTrade(#RequestBody Trade trade, #PathVariable long portfolioId) {
...
}
The easy solution is to create a payload class that has the str1 and the str2 as attributes:
#Getter
#Setter
public class ObjHolder{
String str1;
String str2;
}
And after you can pass
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjHolder Str) {}
and the body of your request is:
{
"str1": "test one",
"str2": "two test"
}
#RequestParam is the HTTP GET or POST parameter sent by client, request mapping is a segment of URL which's variable:
http:/host/form_edit?param1=val1¶m2=val2
var1 & var2 are request params.
http:/host/form/{params}
{params} is a request mapping. you could call your service like : http:/host/form/user or http:/host/form/firm
where firm & user are used as Pathvariable.
Instead of using json, you can do simple thing.
$.post("${pageContext.servletContext.contextPath}/Test",
{
"str1": "test one",
"str2": "two test",
<other form data>
},
function(j)
{
<j is the string you will return from the controller function.>
});
Now in the controller you need to map the ajax request as below:
#RequestMapping(value="/Test", method=RequestMethod.POST)
#ResponseBody
public String calculateTestData(#RequestParam("str1") String str1, #RequestParam("str2") String str2, HttpServletRequest request, HttpServletResponse response){
<perform the task here and return the String result.>
return "xyz";
}
Hope this helps you.
I have adapted the solution of Biju:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
private ObjectMapper om = new ObjectMapper();
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String jsonBody = getRequestBody(webRequest);
JsonNode rootNode = om.readTree(jsonBody);
JsonNode node = rootNode.path(parameter.getParameterName());
return om.readValue(node.toString(), parameter.getParameterType());
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) webRequest.getAttribute(JSONBODYATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
if (jsonBody==null){
try {
jsonBody = IOUtils.toString(servletRequest.getInputStream());
webRequest.setAttribute(JSONBODYATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return jsonBody;
}
}
What's the different:
I'm using Jackson to convert json
I don't need a value in the annotation, you can read the name of the
parameter out of the MethodParameter
I also read the type of the parameter out of the Methodparameter => so the solution should be generic (i tested it with string and DTOs)
BR
Not sure where you add the json but if i do it like this with angular it works without the requestBody:
angluar:
const params: HttpParams = new HttpParams().set('str1','val1').set('str2', ;val2;);
return this.http.post<any>( this.urlMatch, params , { observe: 'response' } );
java:
#PostMapping(URL_MATCH)
public ResponseEntity<Void> match(Long str1, Long str2) {
log.debug("found: {} and {}", str1, str2);
}
You can also use a MultiValue Map to hold the requestBody in.
here is the example for it.
foosId -> pathVariable
user -> extracted from the Map of request Body
unlike the #RequestBody annotation when using a Map to hold the request body we need to annotate with #RequestParam
and send the user in the Json RequestBody
#RequestMapping(value = "v1/test/foos/{foosId}", method = RequestMethod.POST, headers = "Accept=application"
+ "/json",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE ,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public String postFoos(#PathVariable final Map<String, String> pathParam,
#RequestParam final MultiValueMap<String, String> requestBody) {
return "Post some Foos " + pathParam.get("foosId") + " " + requestBody.get("user");
}
Use an inner class
#RestController
public class MyController {
#PutMapping("/do-thing")
public void updateFindings(#RequestBody Bodies.DoThing body) {
...
}
private static class Bodies {
public static class DoThing {
public String name;
public List<String> listOfThings;
}
}
}
request parameter exist for both GET and POST ,For Get it will get appended as query string to URL but for POST it is within Request Body
Good.
I suggest creating a Value Object (Vo) that contains the fields you need. The code is simpler, we do not change the functioning of Jackson and it is even easier to understand.
Regards!
You can achieve what you want by using #RequestParam. For this you should do the following:
Declare the RequestParams parameters that represent your objects and set the required option to false if you want to be able to send a null value.
On the frontend, stringify the objects that you want to send and include them as request parameters.
On the backend turn the JSON strings back into the objects they represent using Jackson ObjectMapper or something like that, and voila!
I know, its a bit of a hack but it works! ;)
you can also user #RequestBody Map<String, String> params,then use params.get("key") to get the value of parameter
If somebody is interested in the webflux solution, below is a reactive version, based on Biju answer.
Please note that there is one very small but synchronized chunk, needed to protect the body from being consumed more than once. If you prefer a fully non-blocking version, I suggest publishing the flux that obtains json on the same scheduler, to make checking and reading sequential.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
#Slf4j
#RequiredArgsConstructor
public class JsonArgumentResolver implements HandlerMethodArgumentResolver {
private static final String ATTRIBUTE_KEY = "BODY_TOSTRING_RESOLVER";
private final ObjectMapper objectMapper;
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArgument.class);
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
String fieldName = parameter.getParameterName();
Class<?> clz = parameter.getParameterType();
return getRequestBody(exchange).map(body -> {
try {
JsonNode jsonNode = objectMapper.readTree(body).get(fieldName);
String s = jsonNode.toString();
return objectMapper.readValue(s, clz);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
});
}
private Mono<String> getRequestBody(ServerWebExchange exchange) {
Mono<String> bodyReceiver;
synchronized (exchange) {
bodyReceiver = exchange.getAttribute(ATTRIBUTE_KEY);
if (bodyReceiver == null) {
bodyReceiver = exchange.getRequest().getBody()
.map(this::convertToString)
.single()
.cache();
exchange.getAttributes().put(ATTRIBUTE_KEY, bodyReceiver);
}
}
return bodyReceiver;
}
private String convertToString(DataBuffer dataBuffer) {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
}
}