o--apg
Last Updated: August 21, 2018
·
43.6K
· jeroenr

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 :)

8 Responses
Add your response

17728

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

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

over 1 year ago ·
19341

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

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

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) :(

over 1 year ago ·
28734

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" ... ]}

over 1 year 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.

over 1 year ago ·
30436

In response to @chandsir, Ì had to change:

def toMap: Map[String,Any] = JsonUtil.toMap(unMarshallMe)

to
def toMapAny: Map[String,Any] = JsonUtil.toMap[Any](unMarshallMe)

and
val jeroenMap = jeroenJson.toMap
to
val jeroenMap = jeroenJson.toMapAny

And it worked.

about 2 months ago ·