I have created an API with a Map<String, Integer> parameter, like this:
#RequestMapping(value = "upload", method = RequestMethod.POST)
public ResponseEntity<String> handleContactsFileUpload(#RequestParam("file") MultipartFile file,
#RequestParam("name") String name,
#RequestParam("campaignAppItemId") Long campaignAppItemId,
#RequestParam("fileColumnHeaders") Map<String,Integer> fileColumnHeaders) throws Exception {
if (file == null)
return new ResponseEntity<>("No file uploaded", HttpStatus.BAD_REQUEST);
contactService.handleContactsFile(file, name, campaignAppItemId,fileColumnHeaders);
return new ResponseEntity<>("File uploaded successfully", HttpStatus.OK);
}
I am trying to call this via Postman:
I passed the fileColumnHeaders inside Body->Form Data as in the screenshot.
Then I got a message like this in Postman:
Failed to convert value of type 'java.lang.String' to required type 'java.util.Map'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found.
Anybody know why this message came ?
How can we pass a map as a parameter in Rest API request?
How can we pass a map through Postman?
You could use #RequestBody instead of #RequestParam for Maps and other non trivial data types and objects - this way spring will map the JSON representing your map parameter to a domain object, which is then serializable and can be converted to a java object.
... Or simply create a converter:
#Component
#RequiredArgsConstructor
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
private final ObjectMapper objectMapper;
#Override
public Map<String, Object> convert(String source) {
try {
return objectMapper.readValue(source, new TypeReference<Map<String, String>>() {
});
} catch (final IOException e) {
return null;
}
}
}
Firstly, you create DTO object to get all data from your request.
public class FormDataDTO {
private MultipartFile file;
private String name;
private Long campaignAppItemId;
private Map<String,Integer> fileColumnHeaders;
// getters, setters
}
Secondly, you can map FormDataDTO from your request without any annotation:
#RequestMapping(value = "upload", method = RequestMethod.POST)
public ResponseEntity<String> handleContactsFileUpload(FormDataDTO formDataDTO){
// your logic code here
}
Finally, form-data in your request will be:
I think this could work:
#RequestMapping(value = "upload/{fileColumnHeaders}", method = RequestMethod.POST)
public ResponseEntity<String> handleContactsFileUpload(#RequestParam("file") MultipartFile file,
#RequestParam("name") String name,
#RequestParam("campaignAppItemId") Long campaignAppItemId,
#MatrixVariable Map<String,Integer> fileColumnHeaders) throws Exception {
if (file == null)
return new ResponseEntity<>("No file uploaded", HttpStatus.BAD_REQUEST);
contactService.handleContactsFile(file, name, campaignAppItemId,fileColumnHeaders);
return new ResponseEntity<>("File uploaded successfully", HttpStatus.OK);
}
Put all other parameters into the body, but add the fileColumnHeaders to the URL like this:
/upload/firstName=1;lastName=2;address=3;phone=4
You will also need this extra configuration:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
Related
Have a problem with optimizing search request.
I have search method that accepts parameters in url query like:
http://localhost:8080/api?code.<type>=<value>&name=Test
Example: http://localhost:8080/api?code.phone=9999999999&name=Test
Defined SearchDto:
public class SearchDto {
String name;
List<Code> code;
}
Defined Code class:
public class Code {
String type;
String value;
}
Currently I'm using Map<String,String> as incoming parameter for the method:
#GetMapping("/search")
public ResponseEntity<?> search(final #RequestParam Map<String, String> searchParams) {
return service.search(searchParams);
}
Then manually converting map values for SearchDto class. Is it possible to get rid of Map<String,String> and pass SearchDto directly as argument in controller method?
Passing a json in querystring is actually a bad practice, since it decrease the security and sets limits on the number of parameters you can send to your endpoint.
Technically speaking, you could make everything work by using your DTO as a controller's parameter, then URL encoding the json before you send it to the backend.
The best option, in your case, is to serve an endpoint that listen to a POST request: it is not an error, neither a bad practise, to use POST when performing a search.
you can customize a HandlerMethodArgumentResolver to implement it.
but , if you want a object receive incoming parameter. why not use POST
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Example {
}
public class ExampleArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
Example requestParam = parameter.getParameterAnnotation(Example.class);
return requestParam != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, #Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, #Nullable WebDataBinderFactory binderFactory) throws Exception {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
Map<String, String[]> parameterMap = webRequest.getParameterMap();
Map<String, String> result = CollectionUtils.newLinkedHashMap(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
}
});
//here will return a map object. then you convert map to your object, I don't know how to convert , but you have achieve it.
return o;
}
}
add to container
#Configuration
#EnableWebMvc
public class ExampleMvcConfiguration implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new ExampleArgumentResolver());
}
}
usage
#RestController
public class TestCtrl {
#GetMapping("api")
public Object gg(#Example SearchDto searchDto) {
System.out.println(searchDto);
return "1";
}
#Data
public static class SearchDto {
String name;
List<Code> code;
}
#Data
public static class Code {
String type;
String value;
}
}
Here is a demo.
this my method signature
#RequestMapping(value = {"/article", "/article/{id}", "/article/{name}"}, method = RequestMethod.GET,
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<JsonNode> get(#PathVariable Map<String, String> pathVarsMap, #RequestParam(value="test") MultiValueMap<String, String> test, #RequestBody(required=false) JsonNode requestBody )
I want to make this into
public ResponseEntity<JsonNode> get( MyStructure mystr)
where MyStructure will have #PathVariable Map<String, String> pathVarsMap, #RequestParam(value="test") MultiValueMap<String, String> test, #RequestBody(required=false) JsonNode requestBody inside of it.
I know that I have to use custom resolvers and implement resolveArgument. One of the examples i saw did (Map<String, String>) httpServletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE). But im not sure how to get it to work. Can i create MultiValueMap and RequestBody inside MyString ?
In another place, I see that the recommendation is to use
#Nonnull
protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
#SuppressWarnings("unchecked")
Map<String, String> variables =
(Map<String, String>) request.getAttribute(
URI_TEMPLATE_VARIABLES_ATTRIBUTE, SCOPE_REQUEST);
return (variables != null) ? variables : Collections.<String, String>emptyMap();
}
so im a bit confused on how should i be implementing this
All #PathVariable , #RequestParam and #RequestBody can only be annotated on the method parameters , so there are no ways for you to annotate them on the object fields.
The codes of the existing HandlerMethodArgumentResolver that resolve the values for these annotations also assume these annotation are annotated on the method parameters ,that means you also cannot simply delegate to them to resolve the value for your request object.
Your best bet is to simply reference the corresponding HandlerMethodArgumentResolver for each annotation and copy the related codes to your implementation.
For #PathVariable , it is resolved by PathVariableMapMethodArgumentResolver
For #RequestParam on MultiValueMap , it is resolved by RequestParamMapMethodArgumentResolver
For #RequestBody , it is resolved by RequestResponseBodyMethodProcessor . Internally , it works with a list of HttpMessageConverter to read the HTTP request body. As you are now using Jackson to read the request body , you only need to focus on MappingJackson2HttpMessageConverter for simplicity.
It is easier than I expected. There following implementation should be a good starting point for you.
First define MyStructure class :
public class MyStructure {
public Map<String, String> pathVariables;
public MultiValueMap<String, String> queryParameters;
public JsonNode requestBody;
}
And implement MyStructureArgumentResolver :
public class MyStructureArgumentResolver implements HandlerMethodArgumentResolver {
private MappingJackson2HttpMessageConverter messageConverter;
public MyStructureArgumentResolver(MappingJackson2HttpMessageConverter messageConverter) {
super();
this.messageConverter = messageConverter;
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyStructure.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
MyStructure request = new MyStructure();
request.queryParameters = resolveQueryParameters(webRequest);
request.pathVariables = resolvePathVariables(webRequest);
request.requestBody = resolveRequestBody(webRequest, parameter);
return request;
}
private MultiValueMap<String, String> resolveQueryParameters(NativeWebRequest webRequest) {
// resolve all query parameter into MultiValueMap
Map<String, String[]> parameterMap = webRequest.getParameterMap();
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
result.add(key, value);
}
});
return result;
}
private Map<String, String> resolvePathVariables(NativeWebRequest webRequest) {
Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (!CollectionUtils.isEmpty(uriTemplateVars)) {
return new LinkedHashMap<>(uriTemplateVars);
} else {
return Collections.emptyMap();
}
}
private JsonNode resolveRequestBody(NativeWebRequest webRequest, MethodParameter parameter)
throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);
MediaType contentType;
try {
contentType = inputMessage.getHeaders().getContentType();
} catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class<?> contextClass = parameter.getContainingClass();
JsonNode body = JsonNodeFactory.instance.objectNode();
if (messageConverter.canRead(JsonNode.class, contextClass, contentType)) {
body = (JsonNode) messageConverter.read(JsonNode.class, inputMessage);
}
return body;
}
}
Then register MyStructureArgumentResolver :
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
private MappingJackson2HttpMessageConverter messageConverter;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new MyStructureArgumentResolver(messageConverter));
}
}
And use it in the controller method :
#RequestMapping(value = { "/test/{name}" }, method = RequestMethod.GET)
public ResponseEntity<String> test(MyStructure request) {
}
#PostMapping("/get")
public ResponseEntity<JsonNode> get( #RequestBody MyStructure mystr){...}
When call this api, fill in params in request body, send body as application/json. Refer to this sample: sample project
I have a sample RestController in Spring Boot:
#RestController
#RequestMapping("/api")
class MyRestController
{
#GetMapping(path = "/hello")
public JSONObject sayHello()
{
return new JSONObject("{'aa':'bb'}");
}
}
I am using the JSON library org.json
When I hit API /hello, I get an exception saying :
Servlet.service() for servlet [dispatcherServlet] in context with path
[] threw exception [Request processing failed; nested exception is
java.lang.IllegalArgumentException: No converter found for return
value of type: class org.json.JSONObject] with root cause
java.lang.IllegalArgumentException: No converter found for return
value of type: class org.json.JSONObject
What is the issue? Can someone explain what exactly is happening?
As you are using Spring Boot web, Jackson dependency is implicit and we do not have to define explicitly. You can check for Jackson dependency in your pom.xml in the dependency hierarchy tab if using eclipse.
And as you have annotated with #RestController there is no need to do explicit json conversion. Just return a POJO and jackson serializer will take care of converting to json. It is equivalent to using #ResponseBody when used with #Controller. Rather than placing #ResponseBody on every controller method we place #RestController instead of vanilla #Controller and #ResponseBody by default is applied on all resources in that controller. Refer this link: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody
The problem you are facing is because the returned object(JSONObject) does not have getter for certain properties. And your intention is not to serialize this JSONObject but instead to serialize a POJO. So just return the POJO.
Refer this link: https://stackoverflow.com/a/35822500/5039001
If you want to return a json serialized string then just return the string. Spring will use StringHttpMessageConverter instead of JSON converter in this case.
The reason why your current approach doesn't work is because Jackson is used by default to serialize and to deserialize objects. However, it doesn't know how to serialize the JSONObject. If you want to create a dynamic JSON structure, you can use a Map, for example:
#GetMapping
public Map<String, String> sayHello() {
HashMap<String, String> map = new HashMap<>();
map.put("key", "value");
map.put("foo", "bar");
map.put("aa", "bb");
return map;
}
This will lead to the following JSON response:
{ "key": "value", "foo": "bar", "aa": "bb" }
This is a bit limited, since it may become a bit more difficult to add child objects. Jackson has its own mechanism though, using ObjectNode and ArrayNode. To use it, you have to autowire ObjectMapper in your service/controller. Then you can use:
#GetMapping
public ObjectNode sayHello() {
ObjectNode objectNode = mapper.createObjectNode();
objectNode.put("key", "value");
objectNode.put("foo", "bar");
objectNode.put("number", 42);
return objectNode;
}
This approach allows you to add child objects, arrays, and use all various types.
You can either return a response as String as suggested by #vagaasen or you can use ResponseEntity Object provided by Spring as below. By this way you can also return Http status code which is more helpful in webservice call.
#RestController
#RequestMapping("/api")
public class MyRestController
{
#GetMapping(path = "/hello", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> sayHello()
{
//Get data from service layer into entityList.
List<JSONObject> entities = new ArrayList<JSONObject>();
for (Entity n : entityList) {
JSONObject entity = new JSONObject();
entity.put("aa", "bb");
entities.add(entity);
}
return new ResponseEntity<Object>(entities, HttpStatus.OK);
}
}
you can also use a hashmap for this
#GetMapping
public Map<String, Object> get() {
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("results", somePOJO);
return map;
}
More correct create DTO for API queries, for example entityDTO:
Default response OK with list of entities:
#GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
return entityService.getAllEntities();
}
But if you need return different Map parameters you can use next two examples
2. For return one parameter like map:
#GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
return ResponseEntity.status(HttpStatus.CREATED).body(
Collections.singletonMap("key", "value"));
}
And if you need return map of some parameters(since Java 9):
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getSomeParameters() {
return ResponseEntity.status(HttpStatus.OK).body(Map.of(
"key-1", "value-1",
"key-2", "value-2",
"key-3", "value-3"));
}
#RequestMapping("/api/status")
public Map doSomething()
{
return Collections.singletonMap("status", myService.doSomething());
}
PS. Works only for 1 value
If you need to return a JSON object using a String, then the following should work:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.ResponseEntity;
...
#RestController
#RequestMapping("/student")
public class StudentController {
#GetMapping
#RequestMapping("/")
public ResponseEntity<JsonNode> get() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readTree("{\"id\": \"132\", \"name\": \"Alice\"}");
return ResponseEntity.ok(json);
}
...
}
use ResponseEntity<ResponseBean>
Here you can use ResponseBean or Any java bean as you like to return your api response and it is the best practice. I have used Enum for response. it will return status code and status message of API.
#GetMapping(path = "/login")
public ResponseEntity<ServiceStatus> restApiExample(HttpServletRequest request,
HttpServletResponse response) {
String username = request.getParameter("username");
String password = request.getParameter("password");
loginService.login(username, password, request);
return new ResponseEntity<ServiceStatus>(ServiceStatus.LOGIN_SUCCESS,
HttpStatus.ACCEPTED);
}
for response ServiceStatus or(ResponseBody)
public enum ServiceStatus {
LOGIN_SUCCESS(0, "Login success"),
private final int id;
private final String message;
//Enum constructor
ServiceStatus(int id, String message) {
this.id = id;
this.message = message;
}
public int getId() {
return id;
}
public String getMessage() {
return message;
}
}
Spring REST API should have below key in response
Status Code
Message
you will get final response below
{
"StatusCode" : "0",
"Message":"Login success"
}
you can use ResponseBody(java POJO, ENUM,etc..) as per your requirement.
I use to return Map<String,Object> in the Controller by using the toMap() method of org.json.JSONObject as follows.
#GetMapping("/json")
public Map<String, Object> getJsonOutput() {
JSONObject jsonObject = new JSONObject();
//construct jsonObject here
return jsonObject.toMap();
}
you can do this :
#RestController
#RequestMapping("/api")
class MyRestController
{
#GetMapping(path = "/hello")
public JSONObject sayHello()
{
return new JSONObject("{'aa':'bb'}").toMap();;
}
}
Our company is planning to switch our microservice technology to Spring Boot. As an initiative I did some advanced reading and noting down its potential impact and syntax equivalents. I also started porting the smallest service we had as a side project.
One issue that blocked my progress was trying to convert our Json request/response exchange to Spring Boot.
Here's an example of the code: (This is Nutz framework for those who don't recognize this)
#POST
#At // These two lines are equivalent to #PostMapping("/create")
#AdaptBy(type=JsonAdapter.class)
public Object create(#Param("param_1") String param1, #Param("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
On PostMan or any other REST client I simply pass POST:
{
"param_1" : "test",
"param_2" : 1
}
I got as far as doing this in Spring Boot:
#PostMapping("/create")
public Object create(#RequestParam("param_1") String param1, #RequestParam("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
I am not sure how to do something similar as JsonAdapter here. Spring doesn't recognize the data I passed.
I tried this but based on the examples it expects the Json paramters to be of an Entity's form.
#RequestMapping(path="/wallet", consumes="application/json", produces="application/json")
But I only got it to work if I do something like this:
public Object (#RequestBody MyModel1 model1) {}
My issue with this is that MyModel1 may not necessarily contain the fields/parameters that my json data has.
The very useful thing about Nutz is that if I removed JsonAdapter it behaves like a regular form request endpoint in spring.
I couldn't find an answer here in Stack or if possible I'm calling it differently than what existing spring devs call it.
Our bosses expect us (unrealistically) to implement these changes without forcing front-end developers to adjust to these changes. (Autonomy and all that jazz). If this is unavoidable what would be the sensible explanation for this?
In that case you can use Map class to read input json, like
#PostMapping("/create")
public Object create(#RequestBody Map<String, ?> input) {
sout(input.get("param1")) // cast to String, int, ..
}
I actually figured out a more straightforward solution.
Apparently this works:
#PostMapping("/endpoint")
public Object endpoint(#RequestBody MyWebRequestObject request) {
String value1 = request.getValue_1();
String value2 = request.getValue_2();
}
The json payload is this:
{
"value_1" : "hello",
"value_2" : "world"
}
This works if MyRequestObject is mapped like the json request object like so. Example:
public class MyWebRequestObject {
String value_1;
String value_2
}
Unmapped values are ignored. Spring is smart like that.
I know this is right back where I started but since we introduced a service layer for the rest control to interact with, it made sense to create our own request model object (DTOs) that is separate from the persistence model.
You can use #RequestBody Map as a parameter for #PostMapping, #PutMapping and #PatchMapping. For #GetMapping and #DeleteMapping, you can write a class which implements Converter to convert from json-formed request parameters to Map. And you would register that class as a bean with #Component annotation. Then you can bind your parameters to #RequestParameter Map.
Here is an example of Converter below.
#Component
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
private final ObjectMapper objectMapper;
#Autowired
public StringToMapConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Map<String, Object> convert(String source) {
try {
return objectMapper.readValue(source, new TypeReference<Map<String, Object>>(){});
} catch (IOException e) {
return new HashMap<>();
}
}
}
If you want to exclude specific field of your MyModel1 class, use #JsonIgnore annotation onto the field like below.
class MyModel1 {
private field1;
#JsonIgnore field2;
}
Then, I guess you can just use what you have done.(I'm not sure.)
public Object (#RequestBody MyModel1 model1) {}
i think that you can use a strategy that involve dto
https://auth0.com/blog/automatically-mapping-dto-to-entity-on-spring-boot-apis/
you send a json to your rest api that is map like a dto object, after you can map like an entity or use it for your needs
try this:
Add new annotation JsonParam and implement HandlerMethodArgumentResolver of this, Parse json to map and get data in HandlerMethodArgumentResolver
{
"aaabbcc": "aaa"
}
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonParam {
String value();
}
#Component
public class JsonParamMethodResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonParam.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RepeatedlyRequestWrapper nativeRequest = webRequest.getNativeRequest(RepeatedlyRequestWrapper.class);
if (nativeRequest == null) {
return null;
}
Gson gson = new Gson();
Map<String, Object> response = gson.fromJson(nativeRequest.getReader(), new TypeToken<Map<String, Object>>() {
}.getType());
if (response == null) {
return null;
}
JsonParam parameterAnnotation = parameter.getParameterAnnotation(JsonParam.class);
String value = parameterAnnotation.value();
Class<?> parameterType = parameter.getParameterType();
return response.get(value);
}
}
#Configuration
public class JsonParamConfig extends WebMvcConfigurerAdapter {
#Autowired
JsonParamMethodResolver jsonParamMethodResolver;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(jsonParamMethodResolver);
}
}
#PostMapping("/methodName")
public void methodName(#JsonParam("aaabbcc") String ddeeff) {
System.out.println(username);
}
I have a sample RestController in Spring Boot:
#RestController
#RequestMapping("/api")
class MyRestController
{
#GetMapping(path = "/hello")
public JSONObject sayHello()
{
return new JSONObject("{'aa':'bb'}");
}
}
I am using the JSON library org.json
When I hit API /hello, I get an exception saying :
Servlet.service() for servlet [dispatcherServlet] in context with path
[] threw exception [Request processing failed; nested exception is
java.lang.IllegalArgumentException: No converter found for return
value of type: class org.json.JSONObject] with root cause
java.lang.IllegalArgumentException: No converter found for return
value of type: class org.json.JSONObject
What is the issue? Can someone explain what exactly is happening?
As you are using Spring Boot web, Jackson dependency is implicit and we do not have to define explicitly. You can check for Jackson dependency in your pom.xml in the dependency hierarchy tab if using eclipse.
And as you have annotated with #RestController there is no need to do explicit json conversion. Just return a POJO and jackson serializer will take care of converting to json. It is equivalent to using #ResponseBody when used with #Controller. Rather than placing #ResponseBody on every controller method we place #RestController instead of vanilla #Controller and #ResponseBody by default is applied on all resources in that controller. Refer this link: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody
The problem you are facing is because the returned object(JSONObject) does not have getter for certain properties. And your intention is not to serialize this JSONObject but instead to serialize a POJO. So just return the POJO.
Refer this link: https://stackoverflow.com/a/35822500/5039001
If you want to return a json serialized string then just return the string. Spring will use StringHttpMessageConverter instead of JSON converter in this case.
The reason why your current approach doesn't work is because Jackson is used by default to serialize and to deserialize objects. However, it doesn't know how to serialize the JSONObject. If you want to create a dynamic JSON structure, you can use a Map, for example:
#GetMapping
public Map<String, String> sayHello() {
HashMap<String, String> map = new HashMap<>();
map.put("key", "value");
map.put("foo", "bar");
map.put("aa", "bb");
return map;
}
This will lead to the following JSON response:
{ "key": "value", "foo": "bar", "aa": "bb" }
This is a bit limited, since it may become a bit more difficult to add child objects. Jackson has its own mechanism though, using ObjectNode and ArrayNode. To use it, you have to autowire ObjectMapper in your service/controller. Then you can use:
#GetMapping
public ObjectNode sayHello() {
ObjectNode objectNode = mapper.createObjectNode();
objectNode.put("key", "value");
objectNode.put("foo", "bar");
objectNode.put("number", 42);
return objectNode;
}
This approach allows you to add child objects, arrays, and use all various types.
You can either return a response as String as suggested by #vagaasen or you can use ResponseEntity Object provided by Spring as below. By this way you can also return Http status code which is more helpful in webservice call.
#RestController
#RequestMapping("/api")
public class MyRestController
{
#GetMapping(path = "/hello", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> sayHello()
{
//Get data from service layer into entityList.
List<JSONObject> entities = new ArrayList<JSONObject>();
for (Entity n : entityList) {
JSONObject entity = new JSONObject();
entity.put("aa", "bb");
entities.add(entity);
}
return new ResponseEntity<Object>(entities, HttpStatus.OK);
}
}
you can also use a hashmap for this
#GetMapping
public Map<String, Object> get() {
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("results", somePOJO);
return map;
}
More correct create DTO for API queries, for example entityDTO:
Default response OK with list of entities:
#GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
return entityService.getAllEntities();
}
But if you need return different Map parameters you can use next two examples
2. For return one parameter like map:
#GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
return ResponseEntity.status(HttpStatus.CREATED).body(
Collections.singletonMap("key", "value"));
}
And if you need return map of some parameters(since Java 9):
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getSomeParameters() {
return ResponseEntity.status(HttpStatus.OK).body(Map.of(
"key-1", "value-1",
"key-2", "value-2",
"key-3", "value-3"));
}
#RequestMapping("/api/status")
public Map doSomething()
{
return Collections.singletonMap("status", myService.doSomething());
}
PS. Works only for 1 value
If you need to return a JSON object using a String, then the following should work:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.ResponseEntity;
...
#RestController
#RequestMapping("/student")
public class StudentController {
#GetMapping
#RequestMapping("/")
public ResponseEntity<JsonNode> get() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readTree("{\"id\": \"132\", \"name\": \"Alice\"}");
return ResponseEntity.ok(json);
}
...
}
use ResponseEntity<ResponseBean>
Here you can use ResponseBean or Any java bean as you like to return your api response and it is the best practice. I have used Enum for response. it will return status code and status message of API.
#GetMapping(path = "/login")
public ResponseEntity<ServiceStatus> restApiExample(HttpServletRequest request,
HttpServletResponse response) {
String username = request.getParameter("username");
String password = request.getParameter("password");
loginService.login(username, password, request);
return new ResponseEntity<ServiceStatus>(ServiceStatus.LOGIN_SUCCESS,
HttpStatus.ACCEPTED);
}
for response ServiceStatus or(ResponseBody)
public enum ServiceStatus {
LOGIN_SUCCESS(0, "Login success"),
private final int id;
private final String message;
//Enum constructor
ServiceStatus(int id, String message) {
this.id = id;
this.message = message;
}
public int getId() {
return id;
}
public String getMessage() {
return message;
}
}
Spring REST API should have below key in response
Status Code
Message
you will get final response below
{
"StatusCode" : "0",
"Message":"Login success"
}
you can use ResponseBody(java POJO, ENUM,etc..) as per your requirement.
I use to return Map<String,Object> in the Controller by using the toMap() method of org.json.JSONObject as follows.
#GetMapping("/json")
public Map<String, Object> getJsonOutput() {
JSONObject jsonObject = new JSONObject();
//construct jsonObject here
return jsonObject.toMap();
}
you can do this :
#RestController
#RequestMapping("/api")
class MyRestController
{
#GetMapping(path = "/hello")
public JSONObject sayHello()
{
return new JSONObject("{'aa':'bb'}").toMap();;
}
}