I want to use this code to get some data from Rest API:
public Map<Integer, String> getCategoriesList() {
Map<Integer, String> list = new HashMap<>();
list.put(1, "Electronics");
list.put(2, "Outdoor and Sports");
list.put(3, "Home and Garden");
list.put(4, "Home appliances");
list.put(5, "Air conditioners and heaters");
list.put(6, "IT accessories");
list.put(7, "Photo and Video");
list.put(8, "TV Video and Gaming");
return list;
}
#GetMapping("categories")
public ResponseEntity<List<String>> getCategoriesList() {
return (ResponseEntity<List<String>>) categoriesService.getCategoriesList();
}
I get error: class java.util.HashMap cannot be cast to class org.springframework.http.ResponseEntity
What is the appropriate way to return this data as a response?
You cannot cast one type to another like that...try this
ResponseEntity type:
#GetMapping("categories")
public ResponseEntity<Map<Integer, String>> getCategoriesList() {
return new ResponseEntity<Map<Integer,String>>(categoriesService.getCategoriesList(), HttpStatus.OK);
}
Without ResponseEntity wrapper
#GetMapping("categories")
#ResponseStatus(code = HttpStatus.OK)
public Map<Integer, String> getCategoriesList() {
return categoriesService.getCategoriesList();
}
Since both types of map are known to jackson (I presume that's what you are using in spring for serialization/deserialization), no need to do anything more.
Reference:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html
Related
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();;
}
}
I have a JSON as follows
{
"1.2.3.4": "domain1.com",
"1.2.3.5": "domain2.com"
}
And a model
public class DnsLookup {
#Getter #Setter private Map<String, String> entry;
}
And retrofit query interface
#GET("/dns/")
Call<DnsLookup> getDns(#QueryMap Map<String, String> params);
When I make the query, I get null in dns.
Call<DnsLookup> call = service.getDns(queryMap);
Response<DnsLookup> response;
response = call.execute();
var dns = response.body();
Actually the original JSON is
{
"1.2.3.4": [
"domain1.com", "domain1-1.com"
],
"1.2.3.5": [
"domain2.com"
]
}
And I have tried the following model to only get null. Since the following didn't work, I broke the problem to just key-value pairs. To my surprise, the simple map also didn't work.
public class DnsLookup {
#Getter #Setter private Map<String, List<String>> entry;
}
Am I doing a noob mistake?
Use:
#GET("/dns/")
Call<Map<String, List<String>>> getDns(#QueryMap Map<String, String> params);
Instead of:
#GET("/dns/")
Call<DnsLookup> getDns(#QueryMap Map<String, String> params);
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);
}
}
I am working on two projects , 1) micro service(MSA) and 2)SDK
I am sending a GET request from the MSA to SDK where I expect to get a JAVA class as the return type , but instead I am returned just the FQDN of the class as a String
MSA code :
#SuppressWarnings("unchecked")
#Override
public Map<String, Class<?>> getSettingClasses() {
try {
Map<String, String> queryParam = Maps.newHashMap();
queryParam.put("sid", userContext.getServiceId().toString());
Map<String, Class<?>> map = restClient.get(
commonSettingService.getApplicationCode(commonSettingService.getServiceDefId(userContext
.getServiceId())), MasterMaintenanceEndpoint.GET_SETTING_CLASSES, Map.class,
userContext.getSessionToken(), queryParam);
return map;
} catch (Exception e) {
logger.error("Can not connect to Master Maintenance service", e);
return null;
}
}
SDK code :
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public Map<String, Class<?>> getSettingClasses() {
Map<String, Class<?>> clazz = Maps.newHashMap();
String serviceDefId = getProviderService().getServiceDefId();
String serviceId = userContext.getServiceId().toString();
clazz.put("dtoClass", getProviderService().getDtoClass());
clazz.put("dtoIndexClass", getProviderService().getIndexDtoClass());
return clazz;
}
Service code :
Map<String, Class<?>> masterMaintenanceClassMap = webApiService.getSettingClasses();
Class<?> dtoClass = masterMaintenanceClassMap.get("dtoClass");
Class<?> dtoIndexClass = masterMaintenanceClassMap.get("dtoIndexClass");
Within SDK code , getProviderService().getDtoClass() returns a java.lang.Class type but in Service code , masterMaintenanceClassMap.get("dtoClass") evaluates to a String type whose value is actually FQDN of the class.
Does REST restrict returning a Class ? Is there a workaround for this ?
Note : I am unable to do Class.forName(fqdn) in my service code as it does not have dependency to the class
You can do that straight forward in a SOAP base web service call. But in REST you should better use json structure and reconvert the json into the required class object. You can do that very easily by using available APIs.
You can found some good example in the below link:-
Converting JSON data to Java object
Hope this will help you.
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();;
}
}