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 MessageConverter
s. 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 MessageConverter
s 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.
It is cool that you bggloed about this. I found you on yahoo and I had been searching for information about this. Nice site, thanks for the info.
Awesome! Thanks for posting this snippet, saved me some time!
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?
No license issues. Use the code as you desire. I’m not sure about the array issue. I often use it to return a Map that gets translated into legitimate JSON.
Thanks. I am returning an object of a case class. It’s translates beautifully but for some reason gets wrap with [].
Any idea?
I’m not sure.
On 8/20/13 9:07 AM, “Casey Lucas's Blog”