Spring MVC preparing JSON but loading a page instead - json

I have a controller to send back a JSON Payload
#RequestMapping(value = "/MerchantMonitoringAPI", method = RequestMethod.GET,produces = "application/json")
public String MerchantMonitoring() {
ApplicationContext context =
new ClassPathXmlApplicationContext("Spring-Module.xml");
TopMerchantsDAO topMerchantsDAO = (TopMerchantsDAO) context.getBean("topMerchantsDAO");
TopMerchants topMerchants = topMerchantsDAO.retrieveMerchantList();
for(String temp:topMerchants.getMerchantList())
{
System.out.println(temp);
}
Gson gson = new Gson();
Type type = new TypeToken<TopMerchants>() {}.getType();
String jsonPayload = gson.toJson(topMerchants, type);
System.out.println(jsonPayload);
return jsonPayload;
}
It is trying to redirect me to a view with the page name as the JSON (localhost:8080/{"merchantList":["Apple","Google"]}.jsp)
How to stop this and return the JSON payload ??

Add #RestController on top of the #RequestMapping
#RestController
#RequestMapping(value = "/MerchantMonitoringAPI", method =
RequestMethod.GET,produces = "application/json")
public String MerchantMonitoring() {...}
Because the method is now annotated with #RestController, the objects returned from this methods will go through message conversion to produce a json resource representation for the client.

If you want to have several methods for returning JSON and page in the same class, you can still annotate your class with #Controller, and the method for JSON annotated with #ResponseBody
If you will annotated class with #RestController - all methods inside class will be working like #ResponseBody and class will be like #Controller annotated. Of course, this is a better approach (to not include page and JSON returning methods in one Controller).
Notice! You can use #RestController only for class (instead of #Controller), not for methods.
If you will open source of this annotation, you will see among other things the next:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Controller
#ResponseBody
public #interface RestController {
String value() default "";
}
and ElementType.TYPE has comment:
Class, interface (including annotation type), or enum declaration

Related

Spring #RequestBody not mapping to custom Object

I have this custom Object called Example, that have a org.json.JSONObject as a mongo query.
public class ExampleObject {
private JSONObject query;
private String[] projections;
private Number limit;
// Constructors, getters and setters
}
I'm using a REST controller like this:
#RestController
#RequestMapping("/example")
public class ExampleRestController {
#RequestMapping(value = "/search", method = RequestMethod.POST)
#ResponseBody
public String example(#RequestBody ExampleObject example) {
return "This is an example";
}
And then, I do with Postman the following request:
POST to http://localhost:8080/example/search
with body as follows (I have checked the validity of the JSON with http://jsonlint.com):
{
"query":{
"field1":"value1",
"field2":"value2"
},
"projections":["field3, field4"],
"limit":3
}
The result is: projections and limit on object "example" have setted correctly but query is an empty JSONObject (not null). If I don't send the field query, the JSONObject variable on the object "example" is null.
I don't understand why the field query is not setted fine. I want to Spring maps the #RequestBody json to an ExampleObject.
Spring (particularly Jackson) does not know how to deserialize JSONObject. You can use com.fasterxml.jackson.core.JsonNode instead.

Return json from rest controller using object mapper manually

I have a rest controller returning a json list of objects. When I call method 1) it works as required.
When I need to configure serialisation to ignore certain properties in one request but not the other, I am using mixIn annotation and objectMapper. When I return the object it is in xml instead of json as before. Can anybody help? I realise I am now returning a string but if I want the same respose as 1) do I need to convert string to object and return in responseEntity as before.
1)
#RequestMapping(value = "/search", method = RequestMethod.POST)
public ResponseEntity<List<MyObject>> search(#RequestBody SearchParams searchParams){
List<MyObject> result = myService.find(searchParams);
return new ResponseEntity<List<MyObject>(result, HttpStatus.OK);
}
returns
[
{"prop1":"val1", "prop2":"val2"},
{"prop1":"val3", "prop2":"val4"}
]
2)
#RequestMapping(value = "/search", method = RequestMethod.POST)
public ResponseEntity<String> search(#RequestBody SearchParams searchParams){
List<MyObject> result = myService.find(searchParams);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.getSerializationConfig().addMixInAnnotations(MyObject.class, MyObjectFilter.class);
String json = objectMapper.writeValueAsString(result);
return new ResponseEntity<String>(json, HttpStatus.OK);
}
returns
<data contentType="text/plain;charset=ISO-8859-1" contentLength="*"><![CDATA[
[
{"prop1":"val1", "prop2":"val2"},
{"prop1":"val3", "prop2":"val4"}
]
]]></data>
This appears in xml tab in soapui instead of json tab. Can anybdy help?

Spring boot / Jackson deserializes JSON of wrong type

I'm a litte bit lost, I have to admit. I wrote a Spring Boot (1.3M2) application that receives a JSON object which it needs to store in a database:
#RequestMapping(value = "/fav", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<String> setFavorite(#RequestBody List<Favorite> favorites) {
...
internally this method passes the JSON to another method which stores it line by line in a database:
jdbcTemplate.batchUpdate(INSERT_FAVORITE, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Favorit fav = favorites.get(i);
ps.setString( ...
}
#Override
public int getBatchSize() {
int size = favorites.size();
return size;
}
When I POST a JSON to the controller which does not match the structure of my Favorite-object I only see null values in my database. Obviously Jackson tries its best to convert my JSON into a Java object but fails and sets all values of the object it finds no value for to null.
Then this list of sort of empty objects is written to the database.
I use curl to POST the values
curl -vX POST https://localhost/fav -d #incorrectype.json
This can't be the source of error because it works with a favorite.json. How can I have my controller / Jackson detect if I use a JSON that does not match ?
One solution is to use annotations from javax.validation, and instead of accepting a List in the controller signature, use a custom wrapper along the lines of this (getters/setters omitted):
public class FavoriteList {
#Valid
#NotNull
#Size(min = 1)
private List<Favorite> favorites;
}
then for the Favorite class add the validation as needed, e. g.:
public class Favorite {
#NotNull
private String id;
}
with these changes in place, modify the controller method signature along these lines:
public ResponseEntity<String> setFavorite(#Valid #RequestBody FavoriteList favoritesList) {
This way, input failing validation will throw exceptions before anything in the controller method is executed.

RestEasy, how to prevent marshalling of JAXB annotated POJOs to and from JSON

I started with a service that consumes and produces output in JSON. I use the resteasy-jackson-provider for (de)marshalling which takes its information from the class description. After a while I was asked to add XML as MediaType. So I annotated my DTOs with JAXB annotations and added the resteasy-jaxb-provider. As a result, I observed that the produced JSON output derives from the JAXB annotations which differs from the original format.
I am on RestEasy Version 3.0.4. As described I use the following providers
resteasy-jackson-provider
resteasy-axb-provider.
resteasy-jettison-provider, because I integrated RestEasy into Spring and this provider is a transitive dependency.
I got aware of the problem when I
used XmlElementWrapper for lists and when
I wrote a custom XmlAdapter which serializes a complex data structure Map<String, List<String>>. Requests with XML MediaType are fine. Requests with JSON MediaType cause an exception. Jackson seems to exploit the XmlAdapter for further information. This was not the case before. Jackson was able to marshall the Map without the JAXB annotations.
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "customer" (Class x.y.z.OptionalParametersMapType), not marked as ignorable
at [Source: org.apache.catalina.connector.CoyoteInputStream#77119553; line: 1, column: 131] (through reference chain: x.y.z.Request["optional"]->x.y.zOptionalParametersMapType["customer"]
)
at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:673)
at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:659)
at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1365)
at org.codehaus.jackson.map.deser.BeanDeserializer._handleUnknown(BeanDeserializer.java:725)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:703)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.xc.XmlAdapterJsonDeserializer.deserialize(XmlAdapterJsonDeserializer.java:59)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$FieldProperty.deserializeAndSet(SettableBeanProperty.java:579)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.ObjectMapper._readValue(ObjectMapper.java:2704)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1315)
at org.codehaus.jackson.jaxrs.JacksonJsonProvider.readFrom(JacksonJsonProvider.java:419)
So, how can I prevent RestEasy from using the JAXB annotations for marshalling to and from JSON?
Here is the request class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "domainRecommendationRequest")
public class Request {
#XmlJavaTypeAdapter(OptionalParametersXmlAdapter.class)
private Map<String, List<String>> optional = new HashMap<>();
}
Here is the XmlAdapter:
#Override
public class OptionalParametersXmlAdapter extends XmlAdapter<OptionalParametersMapType, Map<String, List<String>>> {
public OptionalParametersMapType marshal(Map<String, List<String>> v) throws Exception {
OptionalParametersMapType result = new OptionalParametersMapType();
List<OptionalParameterItemType> optionalParameterItemTypes = new ArrayList<>();
Set<String> keySet = v.keySet();
for (String parameterName : keySet) {
OptionalParameterItemType item = new OptionalParameterItemType();
item.name = parameterName;
item.values = v.get(parameterName);
optionalParameterItemTypes.add(item);
}
result.parameter = optionalParameterItemTypes;
return result;
}
}
Here is the wrapper for the map:
public class OptionalParametersMapType {
public List<OptionalParameterItemType> parameter = new ArrayList<>();
}
Here is the actual map entry item:
public class OptionalParameterItemType {
#XmlAttribute
public String name;
#XmlElementWrapper(name = "values")
#XmlElement(name = "value")
public List<String> values = new ArrayList<>();
}
This is what I expect in the JSON request:
{"optional":{"customer":["Mike"]}}
As you can see, I do intend to have a different format in XML.
The problem is resteasy-jackson-provider depends on jackson-module-jaxb-annotations, which is used to map JAXB annotations/annotated classes to JSON. Now in a normal explicit use of ObjectMapper, in order to make use of this module, we would need to explicitly register this module like (See here)
ObjectMapper objectMapper = new ObjectMapper();
JaxbAnnotationModule module = new JaxbAnnotationModule();
objectMapper.registerModule(module);
-- OR --
AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
objectMapper.setAnnotationIntrospector(introspector);
That being said, it appears (not confirmed with any facts, but looks probable) that when the ObjectMapper is being created for your serialization, when the JAXB annotations are noticed, the module is automatically registered.
I don't know of any possible annotations we can use to stop this, but one way to solve this problem is to create a ContextResolver for the ObjectMapper, where we don't register the JAXB module.
#Provider
public class ObjectMapperContextResolver
implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type) {
return new ObjectMapper();
}
}
Once we register that with our JAX-RS application, it will be the context resolver used to get the ObjectMapper. We could configure the ObjectMapper further, but this is just an example. Test it and it works as expected.

The request sent by the client was syntactically incorrect ().+Spring , RESTClient

I am working with Spring MVC using JSON objects. while I am tring to send JSON Object from RESTClient, I am getting
HTTP Status 400 - The request sent by the client was syntactically incorrect ().
This is my controller
ObjectMapper mapper=new ObjectMapper();
#RequestMapping(value = "/addTask", method = RequestMethod.GET)
public ModelAndView addTask(#RequestParam("json") String json) throws JsonParseException, JsonMappingException, IOException
{
System.out.println("Json object from REST : "+json);
Task task=(Task) mapper.readValue(json, Task);
service.addService(task);
return new ModelAndView("Result");
}
My request URL : http://localhost:8080/Prime/addTask
My Json Object :
{"taskName":"nothing","taskId":1234,"taskDesc":"nothing doing"}
Also i tried specifying "Content-Type: application/json" in RESTClient but still am getting the same error
I ran into a similar situation using a JSON string in the request body recently, and using a very similar Spring setup as yours. In my case I wasn't specifying a String parameter and deserialising it myself though, I was letting Spring do that:
#RequestMapping(value = "/myService/{id}", method = RequestMethod.POST)
#ResponseBody
public void myService(#PathVariable(value = "id") Long id, #RequestBody MyJsonValueObject request) {
..
}
I was getting an HTTP error 400 "The request sent by the client was syntactically incorrect" response. Until I realised that there wasn't a default constructor on the #RequestBody MyJsonValueObject so there were problems deserialising it. That problem presented in this way though.
So if you are using POST and objects, and getting errors like this, make sure you have a default constructor! Add some JUnit to be sure you can deserialise that object.
Note: I'm not saying this is the only reason you get this error. The original case used just String (which does have a default constructor !) so it's a little different. But in both cases it appears the request URI appears to have been mapped to the right method, and something has gone wrong trying to extract parameters from the HTTP request.
Try this
Change
#RequestParam("json") String json
To
#RequestBody Task task
If you are not interested in POST method you can try this
change your Controller method from
#RequestMapping(value = "/addTask", method = RequestMethod.GET)
public ModelAndView addTask(#RequestParam("json") String json)
to
#RequestMapping(value = "/addTask/{taskName}/{taskId}/{taskDesc}", method = RequestMethod.GET)
public ModelAndView addTask(#RequestParam("taskName") String taskName,
#RequestParam("taskId") String taskId,#RequestParam("taskDesc") String taskDesc)
and change your URL to
http://localhost:8080/Prime/addTask/mytask/233/testDesc
My problem was due to the incorrect mapping of the #RequestBody object.
My Request Body looks like this
{data: ["1","2","3"]}
I had the following code in my controller
#RequestMapping(value = "/mentee", method = RequestMethod.POST)
public #ResponseBody boolean updateData(#RequestBody List<Integer> objDTO, HttpSession session) {
...
}
This give me HTTP 400 because Spring doesn't know how to bind my Json data to a List.
I changed the RequestBody object to the following
#RequestMapping(value = "/mentee", method = RequestMethod.POST)
public #ResponseBody boolean updateData(#RequestBody ObjectiveDto objDTO, HttpSession session) {
...
}
and defined ObjectiveDto as followed
#ToString
public class ObjectiveDto {
#Getter #Setter
private List<Integer> data;
}
This resolved the HTTP 400 error.