Friday, July 2, 2010

Custom JSON serialization with Spring MVC

With the rise of Spring RESTful support and the existence of JSON libraries in Java, there could come up the problem of JSON serialization of custom domain models to views. In this article, through the example, I want to share my experience on how to serialize some custom model with JSON through Spring MVC configuration.

Spring 3.0 introduces the notion of ContentNegotiatingViewResolver that is a way to configure Spring to respond to certain media types such application/xml or application/json while letting others do their routine job. More details on this could be found at here and here

In this sample, I am going to serialize the instances of the model Customer to be used in an AJAX-based combo box. There are some steps to go through:

  1. Write the custom JSON serializer
  2. Define and configure the JSON serializer factories in configuration files
  3. Configure the content negotiating view resolver to take care of the JSON requests
Step One

The JSON library that I using in this project is Jackson JSON. Simply, we write a custom serializer for Customer model:
public class CustomerSerializer extends JsonSerializer {

 protected final Log logger = LogFactory.getLog(getClass());

 @Override
 public void serialize(Customer value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
   JsonProcessingException {
  jgen.writeStartObject();
  jgen.writeFieldName("id");
  jgen.writeNumber(value.getId());
  jgen.writeFieldName("name");
  jgen.writeString(value.toString());
  jgen.writeEndObject();
  logger.debug("Customer [" + value + "] mapped to JSON");
 }

}


Step Two

Now, I need some configurable factory bean in Spring that would hold my custom domain models for JSON serialization and also it is an instance of CustomSerializerFactory. Simply, I extend it:
public class CustomSerializerFactoryRegistry extends CustomSerializerFactory implements InitializingBean {

 protected final Log logger = LogFactory.getLog(getClass());

 private Map<Class, JsonSerializer> serializers = new HashMap<Class, JsonSerializer>();

 @Override
 public <T> JsonSerializer<T> createSerializer(Class<T> type, SerializationConfig config) {
  JsonSerializer<T> serializer = super.createSerializer(type, config);
  logger.debug("Found serializer [" + serializer + "] for type [" + type + "].");
  return serializer;
 }

 @Override
 public void afterPropertiesSet() throws Exception {
  for (Map.Entry<Class, JsonSerializer> e : serializers.entrySet()) {
   addGenericMapping(e.getKey(), e.getValue());
  }
  logger.info("Registered all serializers: " + serializers);
 }

 public void setSerializers(Map<Class, JsonSerializer> serializers) {
  this.serializers = serializers;
 }

}

With my CustomSerializerFactoryRegistry, I can easily register all the custom JSON serializers that I define for my application.

However, there is a slight tweak in configuration. To configure JSON in Spring configurations, normally, we define a bean of type ObjectMapper. By default this object mapper bean, initializes some serializer factory, however, we need to inject our own serializer factory instance. The problem is that the Object Mapper defines the setter method that does not have a void return type (required by Spring IoC), so, I define a CustomObjectMapper:
public class CustomObjectMapper extends ObjectMapper {

 protected final Log logger = LogFactory.getLog(getClass());

 public void setCustomSerializerFactory(SerializerFactory factory) {
  setSerializerFactory(factory);
  logger.debug("Using [" + factory + "] as the custom Jackson JSON serializer factory.");
 }

}


Now, glueing step one and two, I can write some configuration for the custom JSON serialization beans:
 <bean id="jacksonJsonObjectMapper" class="nl.hajari.wha.service.json.CustomObjectMapper">
  <property name="customSerializerFactory" ref="jacksonJsonCustomSerializerFactory" />
 </bean>

 <bean id="jacksonJsonCustomSerializerFactory"
  class="nl.hajari.wha.service.json.CustomSerializerFactoryRegistry">
  <property name="serializers">
   <map>
    <entry key="nl.hajari.wha.domain.Customer" value-ref="customerSerializer" />
   </map>
  </property>
 </bean>

 <bean id="customerSerializer" class="nl.hajari.wha.service.json.CustomerSerializer" />


Step Three

The last step is to configure the content negotiating view resolver. The most important point on this view resolver is that it should be highest order of processing in Spring MVC chain since it automatically delegates to other view resolvers when the media type does not apply, so:
 <bean
  class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="order" value="1" />
  <property name="mediaTypes">
   <map>
    <entry key="json" value="application/vnd.com.mobilivity+json" />
   </map>
  </property>
  <property name="defaultViews">
   <list>
    <bean
     class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
     <property name="contentType" value="application/vnd.com.mobilivity+json" />
     <property name="objectMapper" ref="jacksonJsonObjectMapper" />
     <property name="renderedAttributes">
      <set>
       <value>items</value>
      </set>
     </property>

    </bean>
   </list>
  </property>
 </bean>



Now, I can serialize my custom domain model such as Customer through Spring MVC REST requests:
@RequestMapping(value = "/customer/list/all", method = RequestMethod.GET)
that can be used for instance in Dojo widgets for a combo box in a view page.



No comments:

Post a Comment