jackson / jaxb marshaller and unmarshaller of JSON in spray 1.0-M7

827 views
Skip to first unread message

Eugene Dzhurinsky

unread,
Jan 13, 2013, 4:25:40 PM1/13/13
to spray...@googlegroups.com
Hello!

Is there anything ready to use in order to integrate spray/spray-json with JAXB annotations and Jackson?

I found few topics for version 1.0-M3, however something might change for version 1.0-M7. Perhaps this is already exists there.

Please advice.

Thanks!

Mathias

unread,
Jan 13, 2013, 4:46:24 PM1/13/13
to spray...@googlegroups.com
Eugene,

no there is nothing yet.
We will release spray-json 2 with lots of improvements soon, but in the short-term we'll have to focus on more important things.

Cheers,
Mathias

---
mat...@spray.io
http://spray.io
> --
>
>

Eugene Dzhurinsky

unread,
Jan 14, 2013, 3:33:25 AM1/14/13
to spray...@googlegroups.com
What I'm trying to do right now is to provide some generic unmarshaller, which will use Jackson for JSON deserialization.

I found jackson-module-scala which seems to do what I need in terms of deserialization. However I don't understand how to integrate it with routing directives like 'entity'. The directive requires presence of unmarshaller of type T, however I will not have it defined in scope (because unmarshaller will bee generic). So it looks like I will need to create my own directive. Am I missing something here?

Eugene Dzhurinsky

unread,
Jan 14, 2013, 4:08:09 AM1/14/13
to spray...@googlegroups.com
So I am stuck here:

I create serializer object:


import _root_.java.lang.reflect.{Type, ParameterizedType}
import com.fasterxml.jackson.core.`type`.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.{CaseClassModule, JacksonModule}
import spray.routing.{RequestEntityExpectedRejection, Directive}
import shapeless._
import spray.routing.directives.BasicDirectives._
import spray.routing.directives.MiscDirectives._
import spray.routing.UnsupportedRequestContentTypeRejection

object JacksonSerializer {

  val module = new JacksonModule with CaseClassModule

  val mapper = new ObjectMapper
  mapper.registerModule(module)

  def serialize[T](src: T): String = {
    mapper.writeValueAsString(src)
  }

  def deserialize[T: Manifest](src: String): T = {
    mapper.readValue(src, typeReference[T])
  }

  private[this] def typeReference[T: Manifest] = new TypeReference[T] {
    override def getType = typeFromManifest(manifest[T])
  }

  private[this] def typeFromManifest(m: Manifest[_]): Type = {
    if (m.typeArguments.isEmpty) {
      m.erasure
    }
    else new ParameterizedType {
      def getRawType = m.erasure

      def getActualTypeArguments = m.typeArguments.map(typeFromManifest).toArray

      def getOwnerType = null
    }
  }

  def jacksonEntity[T: Manifest]: Directive[T :: HNil] = {
    extract(_.request.entity.asString).flatMap[T :: HNil] {
      case value: String => provide(deserialize[T](value))
    } & cancelAllRejections(ofTypes(RequestEntityExpectedRejection.getClass, classOf[UnsupportedRequestContentTypeRejection]))

  }

}

and trying to use it as

  def signup =
    path("signup") {
      post {
        jacksonEntity[RegistrationData] {
          case regData => complete("Ok")
        }
     }
  }

at this point it doesn't compile because of

scala: missing parameter type for expanded function
The argument types of an anonymous function must be fully known. (SLS 8.5)
Expected type was: Manifest[RegistrationData]
        jacksonEntity[RegistrationData] {
                                        ^

Please help

Age Mooij

unread,
Jan 14, 2013, 4:23:20 AM1/14/13
to spray...@googlegroups.com

The way Spray intends for you to use this is to provide an Unmarshaller[T] (and possibly a Marshaller[T]) that implements the Jackson deserialization. Have a look at the documentation for unmarshalling:

http://spray.io/documentation/spray-httpx/unmarshalling/

The Spray unmarshalling infrastructure is meant to be type-specific so I would advise against trying to build generic unmarshallers because it will probably make things harder instead of easier and the unmarshalling result will be some super type that you then still need to match against. Of course there is no problem with sharing code between a hierarchy of specific unmarshallers to get rid of duplication.

Age


--
 
 

Eugene Dzhurinsky

unread,
Jan 14, 2013, 4:28:35 AM1/14/13
to spray...@googlegroups.com
That means I will need to create variety of marshaller objects for every type I'm using within Spray? So given classes A, B, C I will need to create  Marshaller[A], Marshaller[B] and Marshaller[C] objects?

Mathias

unread,
Jan 14, 2013, 4:32:52 AM1/14/13
to spray...@googlegroups.com
Eugene,

no, you don't have to create individual Marshallers for every type.
If there is common code between A, B and C you can provide one marshaller for all of them, which delegates the type specific things to another layer implementing the things that differ between A, B and C.

For example, the SprayJsonSupport trait does exactly this. It defines a Marshaller and Umarshaller for all types for which a JsonFormat exists.

HTH and cheers,
> --
>
>

Age Mooij

unread,
Jan 14, 2013, 4:56:37 AM1/14/13
to spray...@googlegroups.com
Doesn't that just move the requirement to have one unmarshaller per type one level deeper? If you don't have a spray-json RootJsonFormat for A, B, and C, the code is still not going to compile. right?

If you want to unmarshall directly to A (instead of to a shared super type of A, B, and C), than at some point you need something that produces an explicit A, and whether it is an Unmarshaller[A] or an Unmarshaller[A: RootJsonFormat] doesn't change that basic requirement.

In the spray-json case, having an explicit jsonformat for each of your case classes is a matter of one line per class.

Age
> --
>
>

Mathias

unread,
Jan 14, 2013, 5:03:57 AM1/14/13
to spray...@googlegroups.com
Yes, this moves the requirement one level deeper, where the effort to create something type-specific might be less than at the Marshaller level.
As you say with spray-json it could mean one line per type.

However, depending on your (de)serialization logic all that might be required is a Manifest (Scala 2.9) or a ClassTag (Scala 2.10), so you could indeed get away with zero type-specific lines and still be completely type-safe.

Cheers,
> --
>
>

Age Mooij

unread,
Jan 14, 2013, 5:06:52 AM1/14/13
to spray...@googlegroups.com
Ah, right. I forgot about the Manifest/ClassTag option.

Age
> --
>
>

Eugene Dzhurinsky

unread,
Jan 14, 2013, 7:30:41 AM1/14/13
to spray...@googlegroups.com
Actually the classes A, B and C have no common ancestor and they are different, so I can't create single marshaller/unmarshaller there. So the only option is to pass the expected class directly to Jackson's readValue/writeValue method, and I'm not sure how to do that with current routing directives. If you could help me - that would be great!

Eugene Dzhurinsky

unread,
Jan 14, 2013, 7:33:07 AM1/14/13
to spray...@googlegroups.com
The thing is I don't want to use spray-json here, because I have classes annotated with JAXB annotations and that should be enough for serialization/deserialization. So I'm looking for a simple way to extend 'entity(as[Something])' to the case when Something has JAXB annotations, and unmarshaller can deal with it.


On Sunday, January 13, 2013 4:25:40 PM UTC-5, Eugene Dzhurinsky wrote:

Eugene Dzhurinsky

unread,
Jan 14, 2013, 8:23:08 AM1/14/13
to spray...@googlegroups.com
Okay, eventually I came out with the solution:

  object UnmarshallerTemplate {
    def marshaller[T: Manifest] = Marshaller.of[T](MediaTypes.`application/json`) {
      (value, contentType, ctx) => ctx.marshalTo(HttpBody(contentType, serialize[T](value)))
    }

    def unmarshaller[T: Manifest] = Unmarshaller[T](MediaTypes.`application/json`) {
      case HttpBody(contentType, buffer) => deserialize[T](buffer)
    }
  }

  import UnmarshallerTemplate._

  implicit val RegistrationDataUnmarshaller = unmarshaller[RegistrationData]

  implicit val RegistrationDataMarshaller = marshaller[RegistrationData]

  implicit val SecureUserUnmarshaller = unmarshaller[SecureUser]

  implicit val SecureUserMarshaller = marshaller[SecureUser]


and then importing of that class into routing DSL makes it possible to use jackson mappings for free and with single line of code. That's good enough ;)

Mathias

unread,
Jan 15, 2013, 5:40:00 AM1/15/13
to spray...@googlegroups.com
Eugene,

we have not used Jackson ourselves and currently don't have time to look deeper into this.
Maybe someone else can chip in?

Cheers,
> --
>
>

Ryan Bair

unread,
Jan 15, 2013, 7:32:10 AM1/15/13
to spray...@googlegroups.com
Eugene,

Here's what I'm using with Jackson. It hasn't seen any real world duty, but it's working well in dev.

import java.lang.reflect.{Type => JType}
import java.lang.reflect.{ParameterizedType => JParameterizedType}
import reflect._
import runtime.universe._
import runtime.currentMirror
import spray.http.MediaTypes._
import spray.http.HttpBody
import spray.httpx.unmarshalling.Unmarshaller
import spray.httpx.marshalling.Marshaller
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.core.`type`.TypeReference

trait JacksonSupport {
  protected implicit def mapper: ObjectMapper

  private def typeReference[T: TypeTag] = new TypeReference[T] {
    override def getType = jTypeFromType(typeTag[T].tpe)
  }

  private def jTypeFromType(tpe: Type): JType = {
    val typeArgs = tpe match {
      case TypeRef(_, _, args) => args
    }
    val runtimeClass = currentMirror.runtimeClass(tpe)
    if (typeArgs.isEmpty) { runtimeClass }
    else new JParameterizedType {
      def getRawType = runtimeClass

      def getActualTypeArguments = typeArgs.map(jTypeFromType(_)).toArray

      def getOwnerType = runtimeClass.getEnclosingClass
    }
  }

  implicit def jacksonUnmarshaller[A: TypeTag] = Unmarshaller[A](`application/json`) {
    case x: HttpBody =>
      mapper.readValue[A](x.buffer, typeReference[A])
  }

  implicit def jacksonMarshaller[A] = Marshaller[A] { (value, ctx) =>
    ctx.tryAccept(`application/json`) match {
      case Some(contentType) =>
        val bytes = mapper.writeValueAsBytes(value)
        ctx.marshalTo(HttpBody(contentType, bytes))
      case None => ctx.rejectMarshalling(Seq(`application/json`))
    }
  }
}

Hope that helps,
-Ryan


--



David Pratt

unread,
Jan 15, 2013, 10:13:23 AM1/15/13
to spray...@googlegroups.com
We're using something very similar in our production code - we've also added a marker trait (JacksonMarshallable) that allows us to have a single global implicit marshaller for our types - we just have

implicit val jacksonMarshaller: Marshaller[JacksonMarshallable] = ///create the marshaller

imported wherever we need it, and it works great.


--
 
 

Reply all
Reply to author
Forward
0 new messages