This question already has answers here:
Spring MVC 3.2 #ResponseBody interceptor
(4 answers)
Closed 8 years ago.
I'm using a Spring setup using the json mapping converters to send POJO classes as json back to the client.
E.g.:
#RequestMapping
#ResponseBody
public User getUser() {
User user = getUser();
return user;
}
This will return e.g. something like to the client:
{ 'username': 'My username', 'lastname': 'My lastname' }
I want to intercept all my controller actions to wrap the json in something like:
{
'status': 200,
'data': { 'username': 'My username', 'lastname': 'My lastname' }
}
What would be the best approach for this?
Simply, you can create a map like this
#RequestMapping(value = "/user")
#ResponseBody
public Map<String, Object> getUser(){
User user = new User();
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("data", user);
return map;
}
For common usage, you can create a POJO
public static class ResutInfo{
private int status;
private Object data;
//get set}
then write the controller method
#RequestMapping(value = "/user")
#ResponseBody
public ResutInfo getUser2(){
User user = new User();
ResutInfo resutInfo = new ResutInfo();
resutInfo.setStatus(200);
resutInfo.setData(user);
return resutInfo;
}
I find a possible solution for your question by extends MappingJackson2HttpMessageConverter
public class MyMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//outputMessage.getHeaders().add("code", DigestUtils.md5Hex(object.toString()));
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("data", object);
super.writeInternal(map, outputMessage); //To change body of overridden methods use File | Settings | File Templates.
}
}
Then add your own conventer bean to spring-mvc config file
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.xxx.utils.MyMappingJackson2HttpMessageConverter">
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:default-servlet-handler/>
This method does work for each #ResponseBody handler method.
Note that it changes default Spring action, you should think about it.
You can also do something like this
<mvc:annotation-driven> <!-- this is added for validations to work -->
<mvc:message-converters register-defaults="true">
<ref bean="jsonMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="YourDtoWrapper"/>
</bean>
<bean id="YourDtoWrapper" class="com.YourDtoWrapper"/>
inside your YourDtoWrapper class you can do something like this
public void writeValue(JsonGenerator jgen, Object value) throws IOException, JsonGenerationException, JsonMappingException {
SomeDTO<Object> someDto = new SomeDTO<Object>("SUCCESS");//you can use an enum here
someDto.setDto(value);//here dto is a property of someDTO
super.writeValue(jgen, SomeDTO.getAsMap(someDto));
}
this will give u result some thing like this
{
serviceResult: "SUCCESS"
context: null
dto: {
id: 1
firstName: "test"
lastName: "admin"
email: "admin#test.com"
phone: 1234
userName: "admin"
password: null
createdBy: "admin"
createdDate: null
statusId: 1
statusName: "ACTIVE"
organisationId: 1
organisationName: "test"
userTypeId: 1
userTypeName: "ADMIN"
roleIds: [0]
}
}
Related
Is there any way to add serialization for list implementing class having custom attributes?
I am working on Rest service using Spring-boot 1.3. I have to return JSON response as Paged-List or Normal-List, depend on request on Controller. So, I have to keep return type of controller method as generic public List<Employee> getEmployees(int departmentId)
I am implementing list as below (using generics to use for different object lists)
public class PagedList<E> implements List<E> {
private List<E> list;
private long totalRecords; //Getter-setters are added
public PagedList(List<E> list) {
super();
this.list = list;
}
public PagedList(List<E> list, long totalRecords) {
super();
this.list = list;
this.totalRecords = totalRecords;
}
#Override
public boolean add(E element) {
return this.list.add(element);
}
//All other List abstract methods implemented same as above using this.list
}
Added JsonSerializer for same: public class PagedListSerializer extends JsonSerializer<PagedList> with serialization logic in serialize() method. Which is registered using spring-boot jackson customization :
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializerByType(PagedList.class, new PagedListSerializer());
return builder;
}
When I try to return PagedList<Employee>(list, 1000), I am not able to get following response. Its returning same as of normal list. Not executing custom serialization. How to get following paged response?
{
list : [{employeeId: "1", name: "John" }, ... ],
totalRecords : 1000
}
You probably don't need custom deserializer to get this json. Just add #JsonFormat(shape = JsonFormat.Shape.OBJECT) annotation to your class:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public static class PagedList<E> implements List<E> {
#JsonProperty
private List<E> list;
#JsonProperty // no need for this if you have getter-setters
private long totalRecords;
#JsonIgnore
#Override
public boolean isEmpty() {
return false;
}
...
Here is full demo: https://gist.github.com/varren/35c4ede769499b1290f98e39a2f85589
Update after comments:
I think Spring uses Jacksons return mapper.writerFor(List.class).writeValueAsString(new MyList()); Here is demo:
#RestController
#RequestMapping(value="/")
public static class MyRestController {
private static final ObjectMapper mapper = new ObjectMapper();
//returns [] for both 0 and 1
#RequestMapping(value="test", method = RequestMethod.GET)
public List test(#RequestParam int user) {
return user == 0 ? new ArrayList(): new MyList();
}
//returns [] for 0 and expected custom {"empty": true} for 1
#RequestMapping(value="testObj", method = RequestMethod.GET)
public Object testObj(#RequestParam int user) {
return user == 0 ? new ArrayList(): new MyList();
}
// returns expected custom {"empty": true}
#RequestMapping(value="testMyList", method = RequestMethod.GET)
public MyList testMyList() {
return new MyList();
}
// returns expected custom {"empty": true}
#RequestMapping(value="testMyListMapper", method = RequestMethod.GET)
public String testMyListMapper() throws JsonProcessingException {
return mapper.writeValueAsString(new MyList());
}
// returns []
#RequestMapping(value="testMyListMapperListWriter", method = RequestMethod.GET)
public String testMyListMapperListWriter() throws JsonProcessingException {
return mapper.writerFor(List.class).writeValueAsString(new MyList());
}
}
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public static class MyList extends ArrayList {}
So you have to Option 1) return Object instead of List or Option 2) register custom serialifer for List (and not for PageList) builder.serializerByType(List.class, new PagedListSerializer()); like this:
public class PagedListSerializer extends JsonSerializer<List> {
#Override
public void serialize(List valueObj, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if (valueObj instanceof PagedList) {
PagedList value = (PagedList) valueObj;
gen.writeStartObject();
gen.writeNumberField("totalRecords", value.getTotalRecords());
gen.writeObjectField("list", value.getList());
gen.writeEndObject();
}else{
gen.writeStartArray();
for(Object obj : valueObj)
gen.writeObject(obj);
gen.writeEndArray();
}
}
}
You can Create your customObject Mapper and use your serializer there.
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="custom.CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
I have the below Json
{
"user": {
"name": "Ram",
"age": 27
}
}
which I want to de-serialize into an instance of the class
public class User {
private String name;
private int age;
// getters & setters
}
For this, I have used #JsonRootName on class name and something like below
#Configuration
public class JacksonConfig {
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToEnable(DeserializationFeature.UNWRAP_ROOT_VALUE);
return builder;
}
}
But it did not work as expected. If I send something like below, it worked.
{
"name": "Ram",
"age": 27
}
But I want to get the json de-serialized with root name. Can any one please suggest?
I want to spring boot way of doing this.
#JsonRootName is a good start. Use this annotation on User class and then enable UNWRAP_ROOT_VALUE deserialization feature by adding:
spring.jackson.deserialization.UNWRAP_ROOT_VALUE=true
to your application.properties.
Read more about customizing Jackson mapper in Spring Boot Reference
Using ObjectMapper you can resolve this issue easily. Here's what you have to do :
- Annotate User class as given below
#JsonRootName("user")
public class User {
private String name;
private int age;
// getters & setters
}
Create CustomJsonMapper class
public class CustomJsonMapper extends ObjectMapper {
private DeserializationFeature deserializationFeature;
public void setDeserializationFeature (DeserializationFeature deserializationFeature) {
this.deserializationFeature = deserializationFeature;
enable(this.deserializationFeature);
}
}
Equivalent Spring configuration
<bean id="objectMapper" class=" com.cognizant.tranzform.notification.constant.CustomJsonMapper">
<property name="deserializationFeature" ref="deserializationFeature"/>
</bean>
<bean id="deserializationFeature" class="com.fasterxml.jackson.databind.DeserializationFeature"
factory-method="valueOf">
<constructor-arg>
<value>UNWRAP_ROOT_VALUE</value>
</constructor-arg>
</bean>
Using following code you can test
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
ObjectMapper objectMapper = (ObjectMapper) context
.getBean("objectMapper");
String json = "{\"user\":{ \"name\": \"Ram\",\"age\": 27}}";
User user = objectMapper.readValue(json, User.class);
I have Spring MVC app which receives JSON POSTed from Javascript frontend.
Using Jackson 2, custom object mapper only to set ACCEPT_SINGLE_VALUE_AS_ARRAY as true.
Below is my code snippet.
Enable MVC Java config:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, jackson2Converter());
}
#Primary
#Bean
public MappingJackson2HttpMessageConverter jackson2Converter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper());
return converter;
}
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
return objectMapper;
}
}
Controller.java:
#RequestMapping(value = "home", method = RequestMethod.POST, consumes = "application/json")
#ResponseBody
public String submitForm(#RequestBody final Shipment shipment) {
...
}
POJO:
class Shipment implements Serializable {
private String dstState;
private List<String> dstCities;
// getters, setters and default constructor
}
Ajax POST call:
$.ajax({ url: ".../home", type: "POST", data: JSON.stringify(("#shipForm").serializeArray()), contentType: "application/json", dataType: 'json', ....
JSON object posted: mydata: {"dstState":"NV" ,"dstCities":"Las Vegas"}
Upon receiving POST, there is error:
Could not read JSON: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
at [Source: java.io.PushbackInputStream#44733b90; line: 1, column: 90] (through reference chain: com.*.Shipment["dstCities"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
Please point to anything I am missing here
You input json data format is incorrect.
Shipment Json Representation:
Shipment is Json object
String dstState is also Json object
List<String> dstCities is Json Array
Correct Json Data format for your Shipment class is like
[{"dstState":"NV" , "dstCities": ["Las Vegas"]}]
or
[{"dstState":"NV" , "dstCities": ["Las Vegas", "Las Not Vegas"]}]
I am new to Mockito as well as Spring's RestTemplate. I am working on JUnit tests for a functionality which sends a request to a web-service and gets the response through the use of RestTemplate. I want the server to respond with a response that i want so that i can test the functionalities based on this response. I am using Mockito for mocking.
I am not sure where I am going wrong. Am I not creating proper mocks? Is my JSON object mapper not been configured properly?
Configuration file defining the RestTemplate bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="xsStreamMarshaller" />
<property name="unmarshaller" ref="xsStreamMarshaller" />
</bean>
</list>
</property>
</bean>
<bean id="xsStreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"></bean>
</beans>
My DTO's:
import org.codehaus.jackson.annotate.JsonWriteNullProperties;
#JsonWriteNullProperties(false)
public abstract class BaseDTO {
protected boolean error;
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
}
public class ChildDTO extends CommercialBaseDTO {
private String fullName;
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}
Class containing the method to test:
package com.exmpale.mypackage;
import org.springframework.web.client.RestTemplate;
#Component
public class MyUtilClass {
#Autowired
private RestTemplate restTemplate;
public RestTemplate getRestTemplate(){
return restTemplate;
}
public void setRestTemplate(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
// Method to test
public ChildDTO getChildDTO(MyUser myUser, HttpServletRequest request, HttpServletResponse response)
{
response.setContentType("application/json");
//Nothing much here, it takes the myUser and convert into childDTO
ChildDTO childDTO = new MyUtilClass().getDTOFromUser(request, myUser);
//This is the restTemplate that iam trying to mock.
childDTO = restTemplate.postForObject("http://www.google.com", childDTO, ChildDTO.class);
if (childDTO.isError()) {
//Then do some stuff.........
}
return childDTO;
}
}
The JUnit test class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"test-config.xml"})
public class MyUtilClassTest {
#InjectMocks
RestTemplate restTemplate= new RestTemplate();
private MockRestServiceServer mockServer;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
//Creating the mock server
//Add message conveters
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJacksonHttpMessageConverter());
//Create Object mapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure( DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure( SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_FIELDS, true);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_GETTERS,true);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_IS_GETTERS,true);
MappingJacksonHttpMessageConverter jsonMessageConverter = new MappingJacksonHttpMessageConverter();
jsonMessageConverter.setObjectMapper(objectMapper);
messageConverters.add(jsonMessageConverter);
//Set the message converters
restTemplate.setMessageConverters(messageConverters);
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void testGetChildDTO()throws Exception {
MyUtilClass myUtil = new MyUtilClass();
MyUser myUser = new MyUser();
HttpServletRequest request = new HttpServletRequestWrapper(new MockHttpServletRequest());
HttpServletResponse response = new HttpServletResponseWrapper(new MockHttpServletResponse());
//create the mocks for ChildDTO. I want MyUtilClass().getDTOFromUser(request, myUser) to return this.
ChildDTO childDTOMock_One = Mockito.mock(ChildDTO);
//Want this to be returned when restTemplate.postForObject() is called.
ChildDTO childDTOMock_Two = Mockito.mock(ChildDTO.class);
childDTOMock_Two.setError(false);
//create the mocks for userMgntUtils
MyUtilClass myUtilClassMock = Mockito.mock(MyUtilClass.class);
//stub the method getDTOFromUser() to return the mock object. I need this mock to be passed to 'postForObject()'
Mockito.when(myUtilClassMock.getDTOFromUser(request, myUser)).thenReturn(childDTOMock_One);
String responseJSON="{\"error\":false}";
//set the expectation values for mockServer
mockServer.expect( requestTo("http://www.google.com")).andExpect(method(HttpMethod.POST)).andRespond(withSuccess(responseJSON,MediaType.APPLICATION_JSON));
//set the expectation values for restTemplate
Mockito.when(restTemplate.postForObject( "http://www.google.com", childDTOMock_One, ChildDTO.class)).thenReturn(childDTOMock_Two);
TypedUserDTO result = userMgmtUtils.getUserProfileDTO(registerUser, request, response, action);
assertNotNull(result);
}
}
Getting the following exception:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write JSON: No serializer found for class
org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer
and no properties discovered to create BeanSerializer (to avoid
exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
(through reference chain:
com.biogenidec.dto.TypedUserDTO$$EnhancerByMockitoWithCGLIB$$bee3c447["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["defaultAnswer"]);
nested exception is org.codehaus.jackson.map.JsonMappingException: No
serializer found for class
org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer
and no properties discovered to create BeanSerializer (to avoid
exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
(through reference chain:
com.biogenidec.dto.TypedUserDTO$$EnhancerByMockitoWithCGLIB$$bee3c447["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["defaultAnswer"])
And:
Caused by: org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.biogenidec.dto.TypedUserDTO$$EnhancerByMockitoWithCGLIB$$bee3c447["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["defaultAnswer"])
The idea of Mockito is to test the class and none of the dependencies outside of it. So if your testing MyUtilClass you want to mock the RestTemplate class. And your #InjectMocks is on the wrong class see below.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"test-config.xml"})
public class MyUtilClassTest
{
#Mock
private RestTemplate restTemplate;
#InjectMocks
private MyUtilClass myUtilClass;
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
}
#Test
public void testGetChildDTO()throws Exception
{
MyUser myUser = new MyUser();
HttpServletRequest request = new HttpServletRequestWrapper(new MockHttpServletRequest());
HttpServletResponse response = new HttpServletResponseWrapper(new MockHttpServletResponse());
Mockito.when(RestTemplate.postForObject(Mockito.eq("http://www.google.com",
Mockito.any(ChildDTO.class), Mockito.eq(ChildDTO.class)))).thenAnswer(
new Answer<ChildDTO>()
{
#Override
public ChildDTO answer(InvocationOnMock invocation) throws Throwable
{
//The below statement takes the second argument passed into the method and returns it
return (ChildDTO) invocation.getArguments()[1];
}
});
ChildDTO childDTO = myUtilClass.getDTOFromUser(request, myUser);
//then verify that the restTemplate.postForObject mock was called with the correct parameters
Mockito.verify(restTemplate, Mockito.times(1)).postForObject(Mockito.eq("http://www.google.com",
Mockito.eq(childDTO), Mockito.eq(ChildDTO.class));
}
}
Also I find it bad practice to test other frameworks classes, more often then not they already tested their class and your just duplicating their work.
As correctly noted above, to test your method with mockito, it is not necessary to initialize restTemplate.
It is enough to verify that the parameters of the input are correct (if needed) and return the correct mocking object from restTemplate.
We do not test the restTemplate here, we only test our code. This is the purpose of unit tests.
You can do something like this, or something simpler:
#RunWith(value = MockitoJUnitRunner.class)
public class Test {
#InjectMocks
private MyUtilClass testObj;
#Mock
private RestTemplate restTemplate;
#Mock
MyUser myUser;
#Mock
HttpServletRequest request;
#Mock
HttpServletResponse response;
#Test
public void test() throws Exception {
//Configure sample to comparison and verification the result of the method:
ChildDTO sample = getSample();
//configure mocks:
ChildDTO myObject = new ChildDTO();
//configure myObject properties
ResponseEntity<ChildDTO> respEntity = new ResponseEntity<>(
myObject, HttpStatus.ACCEPTED);
when(restTemplate.postForObject(anyString(), Matchers.<HttpEntity<?>>any(),
Matchers.any(Class.class))).thenReturn(respEntity);
//other stuff to configure correct behaviour of mocks request, response e.t.c.
//act:
ChildDTO result = testObj.getChildDTO(myUser, request, response);
//verify that correct parameters were passed into restTemplate method "postForObject":
verify(restTemplate).postForObject(eq("http://www.google.com"), Matchers.<HttpEntity<?>>any(),
eq(ChildDTO.class)).thenReturn(respEntity);
//assert to verify that we got correct result:
assertEquals(sample, result);
}
}
I was creating file upload using ExtJS 4 frontend and Spring 3 as backend. File upload works, but the response from server has wrong content type. When I send {success:true} using Map<String, Object> serialized by Jackson, ExtJS returns error
Uncaught Ext.Error: You're trying to decode an invalid JSON String: <pre style="word-wrap: break-word; white-space: pre-wrap;">{"success":true}</pre>
Why is my response wrapped with <pre> tag? I've searched and found out that I should change response type to text/html for example. But changing content type in servlet response didn't help
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
response.setContentType("text/html");
// File processing
Map<String, Object> jsonResult = new HashMap<String, Object>();
jsonResult.put("success", Boolean.TRUE);
return jsonResult;
}
When I change return value of upload method to String, everything works correctly, but I want to return Map and have it serialized by Jackson
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody String upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
// File processing
return "{success:true}";
}
My Spring configuration
<bean
id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
</bean>
<bean
id="jacksonMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jacksonMessageConverter"/>
<ref bean="stringHttpMessageConverter" />
</list>
</property>
</bean>
How to tell Spring to return correct content type? Why is response of this method incorrect when response of other methods is interpreted correctly?
You need to set the content-type of response as "text/html".
If the content-type is "application/json" will have this problem. It's odd.
You can return boolean if you need only to return value of success :
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody boolean upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
return true; //or false
}
Well, not really the best solution, but it solves the problem. I've created class, which has Map inside and method for adding parameters into Map. Also there is implemented method toString().
public class ExtJSJsonResponse {
/** Parameters to serialize to JSON */
private Map<String, Object> params = new HashMap<String, Object>();
/**
* Add arbitrary parameter for JSON serialization.
* Parameter will be serialized as {"key":"value"};
* #param key name of parameter
* #param value content of parameter
*/
#JsonIgnore
public void addParam(String key, Object value) {
params.put(key, value);
}
/**
* Gets all parameters. Also is annotated with <code>#JsonValue</code>.
* #return all params with keys as map
*/
#JsonValue
public Map<String, Object> getParams() {
return params;
}
/**
* Returns specified parameter by <code>key</code> as string "key":"value"
* #param key parameter key
* #return "key":"value" string or empty string when there is no parameter
* with specified key
*/
private String paramToString(String key) {
return params.containsKey(key)
? "\"" + key + "\":\"" + params.get(key) + "\""
: "";
}
/**
* Manually transforms map parameters to JSON string. Used when ExtJS fails
* to decode Jackson response. i.e. when uploading file.
* #return
*/
#Override
#JsonIgnore
public String toString() {
StringBuilder sb = new StringBuilder("{");
String delimiter = "";
for (String key : params.keySet()) {
sb.append(delimiter);
sb.append(paramToString(key));
delimiter = ",";
}
sb.append("}");
return sb.toString();
}
}
So when Uncaught Ext.Error: You're trying to decode an invalid JSON String occurs, you simply do this
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody String upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
ExtJSJsonResponse response = new ExtJSJsonResponse();
// File processing
response.addParam("success", true);
response.addParam("message", "All OK");
return response.toString();
}
In other methods which doesn't have problem with serialization you can simply call return response; and it will be automatically serialized.
Method toString() will work only for simple classes such as String. For more complicated classes you'll have to change it.
I think you can use the "produces" attribute of Spring's #RequestMapping annotation:
#RequestMapping(value = "/upload", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE)
public #ResponseBody Map<String, Object> upload(
FileUpload uploadItem, BindingResult result, HttpServletResponse response) {
// File processing
Map<String, Object> jsonResult = new HashMap<String, Object>();
jsonResult.put("success", Boolean.TRUE);
return jsonResult;
}
In config file, you should make this Content-Type available:
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<array>
<value>text/html</value>
<value>application/json</value>
</array>
</property>
</bean>
This is available in Spring 3.1.1.RELEASE, maybe in older versions it doesn't work.