<img height="1" width="1" src="https://www.facebook.com/tr?id=1076094119157733&amp;ev=PageView &amp;noscript=1">

Lift JSON & java.util.Set[_]

Posted by Jan Machacek on Fri, Jun 29, 2012

My last article, Akka eye for the Spring guy, infuriatingly glossed over marshalling and unmarshalling. It turns out that these are difficult problems in statically typed languages. You need to tell the compiler about the types of things you will be receiving at runtime. With REST and Spray, that's not too difficult, because the URLs determine the type of the input data. So far so good. Though as always, the devil is in the detail! And in this case, the devil is java.util.Set[_]. Read on to find out how to deal with those in Lift JSON and Spray.

The main challenge is that the default Lift JSON Formatters do not recognise the Java collections---the FieldSerializer attempts to marshal instances of the java.util.Set[_] as if they were scalars. Suppose you have the following type (that you save in Neo4j using Spring Data, but that's just a detail!):

case class User @PersistenceConstructor()(@GraphId var id: java.lang.Long) {

  @Indexed var username: String = _

  @RelatedTo(`type` = "IS_FRIEND", direction = Direction.OUTGOING)
  @Fetch var friends: java.util.Set[User] = _

  ...
}

This object is fetched by the UserActor, whose ActorRef you have in your Spray UserService:

class UserService(implicit actorSystem: ActorSystem)
  extends Directives
  with DefaultMarshallers with DefaultUnmarshallers {

  val route =
    path("user" / LongNumber) { id =&gt;
      get {
        completeWith((userActor ? Get(id)).mapTo[User])
      }
    } ~
    path("user") {
      post {
        content(as[User]) { user =&gt;
          completeWith((userActor ? Merge(user)).mapTo[User])
        }
      }
    }
}

If you try to compile this code, you will get problems with the completeWith calls, because the compiler does not know about an instance of Marshaller[A] and---respectively--- Unmarshaller[A], where A is Future[A], in this example Future[User]. Let's add the type instances of the Marshaller[A] and Unmarshaller[A]:

trait CustomMarshalling extends DefaultMarshallers {
  implicit def liftJsonFormats: Formats =
    DefaultFormats +
    FieldSerializer[AnyRef]

  implicit def liftJsonMarshaller[A &lt;: Product] =
    new SimpleMarshaller[A] {
      val canMarshalTo = ContentType(`application/json`) :: Nil

      def marshal(value: A, contentType: ContentType) = {
        val jsonSource = write(value)
        DefaultMarshallers.StringMarshaller.marshal(jsonSource, contentType)
      }
    }

  implicit def liftJsonUnmarshaller[A: Manifest] =
    new SimpleUnmarshaller[A] {
      val canUnmarshalFrom = ContentTypeRange(`application/json`) :: Nil

      def unmarshal(content: HttpContent) = protect {
        val jsonSource =
          DefaultUnmarshallers.StringUnmarshaller(content).right.get
        parse(jsonSource).extract[A]
      }
    }
}

So far so good. We're only left with the original problem where the Lift serializer treats the fields of type java.util.Set[_] as scalars. We need to add to the implicit Formats variable a Serializer that understands how to deal with the poor Java cousins of the Scala collections.

trait CustomMarshalling extends DefaultMarshallers {
  implicit def liftJsonFormats: Formats =
    DefaultFormats +
    FieldSerializer[AnyRef] +
    javaSetSerializer

  implicit def liftJsonMarshaller[A &lt;: Product] =
    new SimpleMarshaller[A] {
      val canMarshalTo = ContentType(`application/json`) :: Nil

      def marshal(value: A, contentType: ContentType) = {
        val jsonSource = write(value)
        DefaultMarshallers.StringMarshaller.marshal(jsonSource, contentType)
      }
    }

  implicit def liftJsonUnmarshaller[A: Manifest] =
    new SimpleUnmarshaller[A] {
      val canUnmarshalFrom = ContentTypeRange(`application/json`) :: Nil

      def unmarshal(content: HttpContent) = protect {
        val jsonSource =
          DefaultUnmarshallers.StringUnmarshaller(content).right.get
        parse(jsonSource).extract[A]
      }
    }

  def javaSetSerializer[A : Manifest] = new Serializer[java.util.Set[A]] {
    private val setClass = classOf[java.util.Set[A]]

    def deserialize(implicit format: Formats):
      PartialFunction[(TypeInfo, JValue), java.util.Set[A]] = {

      case (TypeInfo(setClass, Some(parametrizedType)), json) =&gt; json match {
        case JArray(vals) =&gt;
          val arguments = parametrizedType.getActualTypeArguments
          val argument = arguments(0).asInstanceOf[Class[AnyRef]]
          val clazz = Manifest.classType[AnyRef](argument)
          val mset: java.util.Set[A] = new util.HashSet[A]()
          vals.foreach { value =&gt;
            mset.add(
              Extraction.extract[AnyRef](value)
                (liftJsonFormats, clazz).asInstanceOf[A])
          }
          mset
        case x =&gt; throw new MappingException("Can't convert " + x + " to Set")
      }
    }

    def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
      case x: java.util.Set[A] =&gt;
        Extraction.decompose(JavaConversions.asScalaSet(x))(liftJsonFormats)
    }
  }
}

Let's dissect the code a little: the javaSetSerializer for some type A also implicitly receives the Manifest about the type A. The serialize function is easy: we simply convert the Java Set into the Scala one and let Lift process it.
The other way is slightly more difficult. We need to extract the type parameter of the Set; and we know that java.util.Set[_] has only one type parameter (which also has to extend AnyRef), so we can write argument = arguments(0).asInstanceOf[Class[AnyRef]]. To successfully deserialize the type parameter, we must get its Manifest, so we call Manifest.classType[AnyRef](argument). Notice the AnyRef type parameter of the classType function. Without it, the type would be Nothing (and making instances of Nothing would be absurd---I mean, would throw an exception). So, we know that we're making a java.util.Set[A]; we have a Manifest of A; so we can proceed to attempt to deserialize those As and add them to the java.util.Set[A], which we ultimately return!

With a little hacking, we can now support JSON marshalling and unmarshalling of instances that contain Java Sets.

Topics: Java, Spray

Posts by Topic

see all

Subscribe to Email Updates