Passing multiple variables in #RequestBody to a Spring MVC controller using Ajax - java

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&param2=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);
}
}

Related

Can I use #RequestBody with multiple model in Spring? [duplicate]

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&param2=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);
}
}

can't pass the list json param correctly

I want to use OKHttp3-based RestTemplate to remotely call the interface queryByIds to get basic user information.
#Configuration
public class CloudConfig {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
the implementing method queryByIds is below:
#GetMapping("/queryByIds")
public GraceJSONResult queryByIds(#RequestParam String userIds) {
if (StringUtils.isBlank(userIds)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_NOT_EXIST_ERROR);
}
List<String> userIdList = JsonUtils.jsonToList(userIds, String.class);
ArrayList<AppUserVO> userVOList = new ArrayList<>();
assert userIdList != null;
userIdList.forEach(id -> {
AppUserVO userInfo = getBasicUserInfo(id);
userVOList.add(userInfo);
});
return GraceJSONResult.ok(userVOList);
}
Here is the bussiness code, the http://user.mootalk.com is switched to localhost using SwitchHosts:
// Get the basic information of each user and put it in userVOList
String userServerUrlExecute = "http://user.mootalk.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(publisherIdSet);
System.out.println(userServerUrlExecute);
// the debugger paused
ResponseEntity<GraceJSONResult> entity =
restTemplate.getForEntity(userServerUrlExecute, GraceJSONResult.class);
Here is my util class:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
/**
* json converter
*/
public class JsonUtils {
private static final ObjectMapper MAPPER = new ObjectMapper();
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static <T> List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
When I debug the code ,it didn't go into the queryByIds method, and the console printed the userServerUrlExecute below:
But if you construct a request in Postman like this:
http://user.mootalk.com:8003/user/queryByIds?userIds=210726G71HSY2YY8, it could go into the queryByIds method but the userIdList turned out to be null.
If you construct a request in Postman like this :
http://user.mootalk.com:8003/user/queryByIds?userIds=[\"210726G71HSY2YY8\",\"200628AFYM7AGWPH\"],it works well.
So what's wrong with my code while passing the param?
Later message1:
Now the last construct request did't go into queryByIds both in Chrome's address bar and Postman, it threw a 400 Bad Request;
I replaced #RequestParam with #RequestBody in queryByIds, it still threw a 400 Bad Request
Latter message2:
Now it works...with the same code. This is really a mystery.
Really I wouldn't just pass in JSON into a query parameter like you are doing, it's nasty and doesn't really follow any sort of RESTFUL API standard.
You have 4 options as I see it:
Change to a HTTP POST request and pass in the JSON Data into the request body (Recommend and follows best practise).
If for whatever reasons your requirements are it needs to be a HTTP GET request and needs to be a query parameter then you need to base64 encode the JSON before passing it in.
?userIds=W1wiMjEwNzI2RzcxSFNZMllZOFwiLFwiMjAwNjI4QUZZTTdBR1dQSFwiXQ==
Again if it has to be a HTTP GET request but it doesn't need to be JSON then I would do a comma separated list of IDs into the query parameter
?userIds=210726G71HSY2YY8,200628AFYM7AGWPH
Just request 1 user id at a time.
Your requirements are probably going to be what determines the approach you take.

Jackson serialiser on Spring boot is stringifying my Strings

I'm sending a JSON to my API as following :
"{}" and I want that it will be interpreted as JSON and not string. Because the API is adding a double quote to my String and the payload becomes
""{}""
I used #Consumes(MediaType.JSON_APPLICATION) and it doesn't work...
#DeleteMapping(value = "/delete")
public String delete(#RequestBody String json) {
JSONObject obj = new JSONObject(json); //This line throws exception
}
Any Idea ?
because you declare the body as String by #RequestBody String json
what you want is
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.ResponseEntity;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.OK;
public ResponseEntity delete(#RequestBody JsonNode json) {
if (!VeryCustomService.isValid(json)) {
throw new ResponseStatusException(BAD_REQUEST, "Invalid json: " + json);
return new ResponseEntity("Success", OK);
}
}
I would also recommend to use ResponseEntity for the response type rather than String, and throw ResponseStatusException if the input is not valid.
In fact, the #Consumes(MediaType.JSON_APPLICATION) is a JAX-RS annotation that not considered while using SPring MVC annotation.
To resolve this issue I shouldn't use
#Consumes(MediaType.JSON_APPLICATION)
#DeleteMapping(value = "/delete")
but I should use:
#DeleteMapping(value = "/delete", consumes = MediaType.JSON_APPLICATION)
This resolve my issue

Using CXF ClientBUilder, how to unmarshal post response parameters into Java class

I'm using CXF ClientBuilder to send POST data to a REST service. The response I get back looks like this right now:
errorCode=206&errorMessage=blah+blah
I want to unmarshal this into fields in a POJO.
The following code block illustrates what I have right now:
public void validateToken(String token) {
WebTarget target = client.target(getHostPort()).path(getPath());
Builder request = target.request(MediaType.APPLICATION_JSON_TYPE);
Form form = new Form();
form.param("TokenID", token);
Response postResponse = request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
System.out.println("postResponse[" + postResponse + "]");
System.out.println("response.text[" + postResponse.readEntity(String.class) + "]");
// CodeAndMessage codeAndMessage = request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), CodeAndMessage.class);
// System.out.println("codeAndMessage[" + codeAndMessage + "]");
}
public static class CodeAndMessage {
private String errorCode;
private String errorMessage;
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
#Override
public String toString() {
return new ToStringBuilder(this).
append("errorCode", getErrorCode()).
append("errorMessage", getErrorMessage()).
build();
}
}
As written right now, I get the response as I originally described. I'm trying to figure out some variation of those last commented-out lines to replace the first "request.post()" and the two following lines, to get the result I'm looking for.
Update:
I did find at least one way to do this, but I don't know if it's the best way.
Form responseForm = request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), Form.class);
System.out.println("responseForm[" + responseForm + "] map[" + responseForm.asMap() + "]");
return new CodeAndMessage().
errorCode(responseForm.asMap().getFirst("errorCode")).
errorMessage(responseForm.asMap().getFirst("errorMessage"));
The key was using the Form object for the response type. With this solution, I still have to reference the field names. Is there a cleaner way to do this?
Update:
I would guess that a cleaner solution would require implementing a MessageBodyReader for this CodeAndMessage class, but I'm not sure yet how to do that.
My MessageBodyReader implementation looks like this:
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.apache.cxf.jaxrs.provider.FormEncodingProvider;
#Provider
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public class StuffResponseReader implements MessageBodyReader<StuffResponse> {
private FormEncodingProvider<Form> formProvider = new FormEncodingProvider<>();
private static final String PROP_ERROR_CODE = "errorCode";
private static final String PROP_ERROR_DESCRIPTION = "errorMessage";
...
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type.isAssignableFrom(StuffResponse.class);
}
#Override
public StuffResponse readFrom(Class<StuffResponse> type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
Form form = formProvider.readFrom(Form.class, Form.class, annotations, mediaType, httpHeaders, entityStream);
MultivaluedMap<String, String> data = form.asMap();
return new StuffResponse().
errorCode(data.getFirst(PROP_ERROR_CODE)).
errorDescription(data.getFirst(PROP_ERROR_DESCRIPTION)).
...;
}
}
When creating the ClientBuilder, I register the MBR like this:
ClientBuilder builder = ClientBuilder.newBuilder().register(StuffResponseReader.class);

Return mocked up JSON from file (JSONObject) in Spring Controller

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();
}

Categories