Sunday 24 June 2012

Scala and Spring MVC - JSON Mapping

So, after my earlier post I've been playing with a little test rig to do RESTful requests in Scala using the Spring MVC framework. After a few issues I've got something that I actually quite like, and it was relatively painless...

The biggest issue I had was that the Jackson JSON mapping - which Spring uses by default - doesn't work well with Scala types. Case classes it outright failed on, and Scala Maps it did weird things with. Everything else was ok though. Obviously this isn't ideal though. As such, I've put together a Spring HttpMessageConverter implementation that uses the Jerkson JSON mapper - which is based on Jackson but adds Scala support into the mix. I've even written the whole thing in Scala, and was quite impressed with how (relatively) painless it was...

So what does it look like? Well - this...
1:  /**
2:   * Implementation of the Spring HttpMessageConverter to convert between Scala types and JSON using the Jerkson mapper
3:   * @tparam T The generic type of the object to convert
4:   */
5:  class MappingJerksonHttpMessageConverter[T] extends HttpMessageConverter[T] {
6:   /**The Logger to use */
7:   val LOG = Logger[this.type]
8:  
9:   /**The media types that we are able to convert */
10:   val mediaTypes = List(MediaType.APPLICATION_JSON)
11:  
12:   /**
13:    * Helper to see if we support the given media type
14:    * @param mediaType the media type to support. If None here then we return True because of a quirk in Spring
15:    * @return True if we support the media type. False if not
16:    */
17:   def isSupported(mediaType: Option[MediaType]) = {
18:    LOG.debug("Comparing requested media type " + mediaType + " against supported list")
19:    mediaType match {
20:     case Some(mediaType) => mediaTypes.contains(mediaType)
21:     case None => true
22:    }
23:   }
24:  
25:   /**
26:    * Determine if we are able to read the requested type
27:    * @param clazz the class type to read
28:    * @param mediaType the media type to read
29:    * @return True if we support the media type. False if not
30:    */
31:   def canRead(clazz: Class[_], mediaType: MediaType) = isSupported(Option(mediaType))
32:  
33:   /**
34:    * Determine if we are able to write the requested type
35:    * @param clazz the class type to write
36:    * @param mediaType the media type to write
37:    * @return True if we support the media type. False if not
38:    */
39:   def canWrite(clazz: Class[_], mediaType: MediaType) = isSupported(Option(mediaType))
40:  
41:   /**
42:    * Get the supported media types
43:    * @return the supported media types, as a Java List
44:    */
45:   def getSupportedMediaTypes = java.util.Arrays.asList(mediaTypes.toArray: _*)
46:  
47:   /**
48:    * Actually attempt to read the data in the input message into the appropriate data type
49:    * @param clazz the class that we are reading into
50:    * @param inputMessage the input message containing the data
51:    * @return the unmarshalled object
52:    */
53:   def read(clazz: Class[_ <: T], inputMessage: HttpInputMessage) = {
54:    LOG.debug("About to read message")
55:    Json.parse[T](inputMessage.getBody)(Manifest.classType(clazz))
56:   }
57:  
58:   /**
59:    * Actually attempt to write the data to the output message
60:    * @param t the value that is to be written
61:    * @param contentType the media type to write as
62:    * @param outputMessage the output message to write to
63:    */
64:   def write(t: T, contentType: MediaType, outputMessage: HttpOutputMessage) {
65:    LOG.debug("About to write message")
66:    val jsonString = Json.generate(t)
67:    val writer = new OutputStreamWriter(outputMessage.getBody)
68:    writer.write(jsonString)
69:    writer.flush()
70:    writer.close()
71:   }
72:  }
73:    

No comments:

Post a Comment