I'm struggling with this:
We have a Table class with a Guava multimap (simplified code, basically 1 member, 2 constructors, getter and setter for the multimap):
public class Table {
private LinkedHashMultimap<String,Field> fields;
public Table(){
this.fields = LinkedHashMultimap.create();
};
public Table (LinkedHashMultimap<String, Field> fields){
this.fields= fields;
}
public LinkedHashMultimap<String, Field> getFields() {
return fields;
}
public void setFields(LinkedHashMultimap<String, Field> fields) {
this.fields = fields;
}
}
And I want to serialise this using Spring MVC 3.2.11 using jackson 2.4.3.
POM relevant dependencies are:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.4.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
<version>2.4.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.11.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.11.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
My spring.xml NOW looks like this (following this example)
<bean id="abstractJacksonObjectMapper"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"
p:targetMethod="registerModule">
<property name="targetObject">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true">
<!--<property name="featuresToDisable">-->
<!--<util:constant static-field="com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES" />-->
<!--</property>-->
</bean>
</property>
<property name="arguments">
<list>
<bean class="com.fasterxml.jackson.datatype.guava.GuavaModule" />
</list>
</property>
</bean>
<bean id="abstractMappingJacksonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"
abstract="true"/>
<bean id="abstractMappingJacksonJsonView"
class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"
abstract="true"
p:extractValueFromSingleKeyModel="true"/>
<bean id="jacksonObjectMapper" parent="abstractJacksonObjectMapper" />
<bean id="mappingJacksonHttpMessageConverter"
parent="abstractMappingJacksonHttpMessageConverter"
p:objectMapper-ref="jacksonObjectMapper"
p:supportedMediaTypes="application/json" />
<bean id="mappingJacksonJsonView"
parent="abstractMappingJacksonJsonView"
p:objectMapper-ref="jacksonObjectMapper"
p:contentType="application/json" />
I also tried this other approach using an extended Jackson2ObjectMapperFactoryBean:
<!-- Json rendering configuration-->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="1" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
<property name="objectMapper">
<bean class="my.package.Jackson2ObjectMapperFactoryBean"/>
</property>
</bean>
</list>
</property>
<property name="ignoreAcceptHeader" value="true" />
</bean>
And then the FactoryBean looks like this:
package my.package;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
public class Jackson2ObjectMapperFactoryBean extends org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean{
public ObjectMapper getObject(){
ObjectMapper objectMapper =super.getObject();
objectMapper.registerModule(new GuavaModule());
return objectMapper;
}
}
I have a Test class that works fine and is not using Spring at all (just testing Table.class + Jackson + guava) Simplified:
Table study = getTable();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new GuavaModule());
String tableString = mapper.writeValueAsString(table);
It serialises it properly:
{
"fields":{
"Field1":[
{
"index":0,
"header":"Field1",
"fieldType":"fieldtype",
"description":null,
"cleanHeader":null
}
],
"Field2":[
{
"index":1,
"header":"Field2",
"fieldType":"fieldtype",
"description":null,
"cleanHeader":null
}
]
}
}
Using spring (any of the 2 approaches) I'm getting:
{
"fields":{
"empty": false
}
}
My controller has a @ResponseBody annotation and it's returning a Table.
EDITED: I'm debugging deep into spring classes (firs time, ;-)) and org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor is handling the request. Is this related to my problem...Is my spring xml, somehow in contradiction with the @ResponseBody annotation?
Any ideas what I'm doing wrong?
NOTE: I need the Multimap, can't be a standard Java collection.
After all I found out that the @ResponseBody annotation was forcing to use a different "viewResolver", that was using Jackson, but without the Guava module.
So, to fix this I removed the @ResponseBody annotation in my Controller method:
Unfortunately, this was returning: {Table:{...}} and was an artefact introduced by the ValueHandlder (ModelAttributeMethodProcessor).
At the end, what is working now is:
1.- Restore the @ResponseBody 2.- Register the Guava Module within the MappingJackson2HttpMessageConverter that come by default with the @ResponseBody handler.
This is how the spring xml looks like: Much clean and simpler: