Using Jerkson with Spring to render JSON for Scala objects

Spring MVC can pretty easily be used to render Java objects as JSON content. But for us (at work), the default out-of-the-box configuration wasn’t ideal for rendering Scala objects. I wanted to be able to write Scala code like:

...
case class ServiceXResponse(id: String, val1: Option[String] = None)

@RequestMapping(Array("/serviceX"))
@ResponseBody
def serviceX(request: HttpServletRequest) = {
  // do some logic but ultimately return an object for json rendering
  ServiceXResponse(id = "123")
  // or ServiceXResponse(id = "123", val1 = Some("value for optional val1"))
}
...

and have JSON rendered like:

{
   id: "123"
}

or

{
   id: "123",
   val1: "value for optional val1"
}

The default implementation that is enabled with Spring 3.1 uses Jackson which didn’t work very well with Scala case classes and Option. I wanted None to be rendered as a non-existent JSON values (vs. null). I wanted to stick with Jackson because of its performance and in-house track-record. After finding and playing around with Jerkson, I decided it would be a better fit for our Spring / Scala scenario. Spring 3.1 makes it pretty easy to configure custom MessageConverters. I used Rossen Stoyanchev’s post and the Spring source to determine how to write a MessageConverter that will use Jerkson:

import org.springframework.http.{MediaType, HttpOutputMessage, HttpInputMessage}
import com.codahale.jerkson.Json
import org.springframework.http.converter.{HttpMessageNotWritableException,
  HttpMessageNotReadableException, AbstractHttpMessageConverter}
import java.nio.charset.Charset
import org.codehaus.jackson.JsonParseException

class JerksonHttpMessageConverter extends
  AbstractHttpMessageConverter[Object]
  (new MediaType("application", "json", Charset.forName("UTF-8"))) {

  val json = new Json {
    def canWrite(clazz : Class[_]) = mapper.canSerialize(clazz)
    def canDeserialize(clazz: Class[_]) =
      mapper.canDeserialize(mapper.constructType(clazz))
  }

  override def writeInternal(o: Object, outputMessage: HttpOutputMessage) = {
    try {
      json.generate(o, outputMessage.getBody)
    } catch {
      case ex: Exception =>
        throw new HttpMessageNotWritableException(
            "Could not write JSON: " + ex.getMessage(), ex);
    }
  }

  override def readInternal(clazz: Class[_ <: Object],
    inputMessage: HttpInputMessage) =  {
    try {
      json.parse(inputMessage.getBody())(Manifest.classType(clazz))
    } catch {
      case ex: JsonParseException =>
        throw new HttpMessageNotReadableException(
            "Could not read JSON: " + ex.getMessage(), ex);
    }
  }

  override def supports(clazz: Class[_]): Boolean = {
    throw new UnsupportedOperationException()
  }

  override def canRead(clazz: Class[_], mediaType: MediaType): Boolean = {
    json.canDeserialize(clazz) && canRead(mediaType)
  }

  override def canWrite(clazz : Class[_], medianType : MediaType) : Boolean = {
    json.canWrite(clazz) && canWrite(medianType)
  }
}

Now, you can replace the default MessageConverters with your own. In your Spring XML file:

<mvc:annotation-driven>
  <mvc:message-converters>
    <bean class="....JerksonHttpMessageConverter"/>
  </mvc:message-converters>
</mvc:annotation-driven>

BTW, since Jerkson has great support for not only Option but also for Scala collections, you’ll be able to use those as well.

About these ads

6 thoughts on “Using Jerkson with Spring to render JSON for Scala objects

  1. thanks for this post. any license issues with the above code?
    BTW, I have a problem that this converter returns a JSON array even when my rest resource returns a single object.
    Any ideas why?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s