o--apg
Last Updated: July 26, 2017
·
31.85K
· jeroenr
Dsc 0061

Easy JSON (un)marshalling in Scala with Jackson

There's a nice add-on module for Jackson to support Scala data types. Here's the dependencies:

libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.2.2",
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.2.2"

I've written a little convenience wrapper around it to demonstrate its use

import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

object JsonUtil {
  val mapper = new ObjectMapper() with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)
  mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

  def toJson(value: Map[Symbol, Any]): String = {
    toJson(value map { case (k,v) => k.name -> v})
  }

  def toJson(value: Any): String = {
    mapper.writeValueAsString(value)
  }

  def toMap[V](json:String)(implicit m: Manifest[V]) = fromJson[Map[String,V]](json)

  def fromJson[T](json: String)(implicit m : Manifest[T]): T = {
    mapper.readValue[T](json)
  }
}

Now you can easily (un)marshall any valid JSON structure:

/*
 * (Un)marshalling a simple map 
 */
 val originalMap = Map("a" -> List(1,2), "b" -> List(3,4,5), "c" -> List())
 val json = JsonUtil.toJson(originalMap)
 // json: String = {"a":[1,2],"b":[3,4,5],"c":[]}
 val map = JsonUtil.toMap[Seq[Int]](json)
 // map: Map[String,Seq[Int]] = Map(a -> List(1, 2), b -> List(3, 4, 5), c -> List())

/*
 * Unmarshalling to a specific type of Map
 */
val mutableSymbolMap = JsonUtil.fromJson[collection.mutable.Map[Symbol,Seq[Int]]](json)
// mutableSymbolMap: scala.collection.mutable.Map[Symbol,Seq[Int]] = Map('b -> List(3, 4, 5), 'a -> List(1, 2), 'c -> List())

/*
 * (Un)marshalling nested case classes
 */
case class Person(name: String, age: Int)
case class Group(name: String, persons: Seq[Person], leader: Person)

val jeroen = Person("Jeroen", 26)
val martin = Person("Martin", 54)

val originalGroup = Group("Scala ppl", Seq(jeroen,martin), martin)
// originalGroup: Group = Group(Scala ppl,List(Person(Jeroen,26), Person(Martin,54)),Person(Martin,54))

val groupJson = JsonUtil.toJson(originalGroup)
// groupJson: String = {"name":"Scala ppl","persons":[{"name":"Jeroen","age":26},{"name":"Martin","age":54}],"leader":{"name":"Martin","age":54}}

val group = JsonUtil.fromJson[Group](groupJson)
// group: Group = Group(Scala ppl,List(Person(Jeroen,26), Person(Martin,54)),Person(Martin,54))

If you really want to have fun, you can even monkey patch Scala classes to simplify (un)marshalling

object MarshallableImplicits {

  implicit class Unmarshallable(unMarshallMe: String) {
    def toMap: Map[String,Any] = JsonUtil.toMap(unMarshallMe)
    def toMapOf[V]()(implicit m: Manifest[V]): Map[String,V] = JsonUtil.toMapOf[V](unMarshallMe)
    def fromJson[T]()(implicit m: Manifest[T]): T =  JsonUtil.fromJson[T](unMarshallMe)
  }

  implicit class Marshallable[T](marshallMe: T) {
    def toJson: String = JsonUtil.toJson(marshallMe)
  }
}

If you import the implicit conversions you can call toJson on any object and toMap or fromJson on any String.

import utils.MarshallableImplicits._

case class Person(name:String, age: Int)

val jeroen = Person("Jeroen", 26)


val jeroenJson = jeroen.toJson
// jeroenJson:  String = {"name":"Jeroen","age":26}

val jeroenMap = jeroenJson.toMap
// jeroenMap: Map[String,Any] = Map(name -> Jeroen, age -> 26)

Happy coding :)

Say Thanks
Respond

7 Responses
Add your response

17728
None

Thanks. Finally figured this out after breaking my head over numerous Scala json libraries that are not as performant or reliable as Jackson. Would have saved me a day or two of trying them all out (especially play json and json4s) if had found this earlier :-)

over 1 year ago ·
18783
None

Thanks for the tutorial! One question: Is mapper.readValue[T](json) thread safe?

over 1 year ago ·
19341
None

I tried described approach, but it fails in my case.
Having Scala 2.10.4 and Jackson libs 2.2.2 the readValuemethod fails on

com.fasterxml.jackson.databind.JsonMappingException: Argument #0 of constructor [constructor for A$A20$A$A20$Group, annotations: [null]] has no property name annotation; must have name when multiple-paramater constructor annotated as Creator

More details here https://gist.github.com/anonymous/7dc05bfdefe7a9c68d9a. Right now I am trying to find out the correct form of annotations.

over 1 year ago ·
19507
None

Update - the issue described seems to be related purely to scala worksheets (I tend to use them for playing and prototyping).

over 1 year ago ·
28733
Be9359fff350f9fddef026a00640945b

Is there a way to do this without the: ... .experimental.ScalaObjectMapper ?

Tried using ObjectMapper, based on this post, but ran into complications with: mapper.readValue[T](json) :(

6 months ago ·
28734
Be9359fff350f9fddef026a00640945b

Had to simplify, but got it to work this way:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import scala.reflect.ClassTag
import scala.reflect._

object JsonUtil {
    val jacksonMapper = new ObjectMapper()
    jacksonMapper.registerModule(DefaultScalaModule)
    //mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

    def toJson(value: Map[Symbol, Any]): String = {
        toJson(value map { case (k,v) => k.name -> v})
    }

    def toJson(value: Any): String = {
        jacksonMapper.writeValueAsString(value)
    }

    def fromJson[T: ClassTag](json: String): T = {
        jacksonMapper.readValue[T](json, classTag[T].runtimeClass.asInstanceOf[Class[T]])
    }
}

And then can parse to a map using:

JsonUtil.fromJson[Map[String, List[Map[String, String]]]](jsonStr1)
for: //{"key": "value"}

JsonUtil.fromJson[Map[String, List[Map[String, String]]]](jsonStr2)
for:
//{"key": [ "subKey": "val" ... ]}

6 months ago ·
29134

val jeroenMap = jeroenJson.toMap

is not compiled anyway. While the compiler complain

Note that implicit conversions are not applicable because they are ambiguous: both method augmentString in object Predef of type (x: String)scala.collection.immutable.StringOps and method Unmarshallable in object MarshallableImplicits of type (unMarshallMe: String)utils.MarshallableImplicits.Unmarshallable are possible conversion functions from JsonTest.jeroenJson.type to ?{def toMap: ?}

Any workaround or suggestion are appreciate.

about 2 months ago ·