Update (18.11.2015): added spray-json-shapeless library

Update (06.11.15): added circe library

Some time ago I wrote a post on relational database access in Scala since I was looking for a library and there were many of them available, making it hard to make a choice. It turns out that the situation is similar if not worse when it comes to JSON libraries in Scala.

There are just plenty of them. You have no idea. (me neither until I wrote this post)

The following is an attempt to provide a quick overview at how a subset of the libraries I found does a few of the most common things one would likely need to do in regard to JSON:

parsing it from a raw string

browsing the AST

building an AST

mapping to a case class

There are of course plenty more valid use-cases but this post is not going to cover those.

Let’s get started!

Scala JSON libraries

play-json

The Play Framework comes with a JSON library that covers most of the common use-cases one would encounter when building web applications:

Parsing raw JSON

scala> import play.api.libs.json._ import play.api.libs.json._ scala> val rawJson = """{"hello": "world", "age": 42}""" rawJson: String = {"hello": "world", "age": 42} scala> Json.parse(rawJson) res0: play.api.libs.json.JsValue = {"hello":"world","age":42} 1 2 3 4 5 6 7 8 9 scala > import play . api . libs . json . _ import play . api . libs . json . _ scala > val rawJson = "" "{" hello ": " world ", " age ": 42}" "" rawJson : String = { "hello" : "world" , "age" : 42 } scala > Json . parse ( rawJson ) res0 : play . api . libs . json . JsValue = { "hello" : "world" , "age" : 42 }

Browsing the AST

scala> (res0 \ "hello").as[String] res1: String = world 1 2 3 scala > ( res0 \ "hello" ) . as [ String ] res1 : String = world

Building a JSON AST

scala> Json.obj("hello" -> "world", "age" -> 42) res2: play.api.libs.json.JsObject = {"hello":"world","age":42} 1 2 3 scala > Json . obj ( "hello" -> "world" , "age" -> 42 ) res2 : play . api . libs . json . JsObject = { "hello" : "world" , "age" : 42 }

Mapping to a case class

scala> case class Model(hello: String, age: Int) defined class Model scala> implicit val modelFormat = Json.format[Model] modelFormat: play.api.libs.json.OFormat[Model] = play.api.libs.json.OFormat$$anon$1@81116d scala> Json.fromJson[Model](res0) res3: play.api.libs.json.JsResult[Model] = JsSuccess(Model(world,42),) scala> res3.get res4: Model = Model(world,42) scala> Json.toJson(Model("bar", 23)) res5: play.api.libs.json.JsValue = {"hello":"bar","age":23} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 scala > case class Model ( hello : String , age : Int ) defined class Model scala > implicit val modelFormat = Json . format [ Model ] modelFormat : play . api . libs . json . OFormat [ Model ] = play . api . libs . json . OFormat $ $ anon $ 1 @ 81116d scala > Json . fromJson [ Model ] ( res0 ) res3 : play . api . libs . json . JsResult [ Model ] = JsSuccess ( Model ( world , 42 ) , ) scala > res3 . get res4 : Model = Model ( world , 42 ) scala > Json . toJson ( Model ( "bar" , 23 ) ) res5 : play . api . libs . json . JsValue = { "hello" : "bar" , "age" : 23 }

lift-json

lift-json is the JSON library of the Lift framework. It is one of the oldest one out there if I’m not mistaken.

Parsing raw JSON

scala> import net.liftweb.json._ import net.liftweb.json._ scala> val rawJson = """{"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}""" rawJson: String = {"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}} scala> parse(rawJson) res0: net.liftweb.json.JValue = JObject(List(JField(hello,JString(world)), JField(age,JInt(42)), JField(nested,JObject(List(JField(deeper,JObject(List(JField(treasure,JBool(true)))))))))) 1 2 3 4 5 6 7 8 9 scala > import net . liftweb . json . _ import net . liftweb . json . _ scala > val rawJson = "" "{" hello ": " world ", " age ": 42, " nested ": { " deeper ": { " treasure ": true }}}" "" rawJson : String = { "hello" : "world" , "age" : 42 , "nested" : { "deeper" : { "treasure" : true } } } scala > parse ( rawJson ) res0 : net . liftweb . json . JValue = JObject ( List ( JField ( hello , JString ( world ) ) , JField ( age , JInt ( 42 ) ) , JField ( nested , JObject ( List ( JField ( deeper , JObject ( List ( JField ( treasure , JBool ( true ) ) ) ) ) ) ) ) ) )

Browsing the AST

scala> res0 \ "nested" \ "deeper" \ "treasure" res1: net.liftweb.json.JsonAST.JValue = JBool(true) 1 2 3 scala > res0 \ "nested" \ "deeper" \ "treasure" res1 : net . liftweb . json . JsonAST . JValue = JBool ( true )

Building a JSON AST

scala> import net.liftweb.json.JsonDSL._ import net.liftweb.json.JsonDSL._ scala> ("hello" -> "world") ~ ("age" -> 42) res2: net.liftweb.json.JsonAST.JObject = JObject(List(JField(hello,JString(world)), JField(age,JInt(42)))) 1 2 3 4 5 6 scala > import net . liftweb . json . JsonDSL . _ import net . liftweb . json . JsonDSL . _ scala > ( "hello" -> "world" ) ~ ( "age" -> 42 ) res2 : net . liftweb . json . JsonAST . JObject = JObject ( List ( JField ( hello , JString ( world ) ) , JField ( age , JInt ( 42 ) ) ) )

Mapping to a case class

object LiftJsonExample { def main(args: Array[String]): Unit = { import net.liftweb.json._ implicit val formats = DefaultFormats case class Model(hello: String, age: Int) val rawJson = """{"hello": "world", "age": 42}""" println(parse(rawJson).extract[Model]) } } 1 2 3 4 5 6 7 8 9 10 11 12 object LiftJsonExample { def main ( args : Array [ String ] ) : Unit = { import net . liftweb . json . _ implicit val formats = DefaultFormats case class Model ( hello : String , age : Int ) val rawJson = "" "{" hello ": " world ", " age ": 42}" "" println ( parse ( rawJson ) . extract [ Model ] ) } }

spray-json

spray rols its own JSON library that focuses on working with case classes:

Parsing raw JSON

scala> import spray.json._ import spray.json._ scala> import DefaultJsonProtocol._ import DefaultJsonProtocol._ scala> val rawJson = """{"hello": "world", "age": 42}""" rawJson: String = {"hello": "world", "age": 42} scala> rawJson.parseJson res0: spray.json.JsValue = {"hello":"world","age":42} 1 2 3 4 5 6 7 8 9 10 11 12 scala > import spray . json . _ import spray . json . _ scala > import DefaultJsonProtocol . _ import DefaultJsonProtocol . _ scala > val rawJson = "" "{" hello ": " world ", " age ": 42}" "" rawJson : String = { "hello" : "world" , "age" : 42 } scala > rawJson . parseJson res0 : spray . json . JsValue = { "hello" : "world" , "age" : 42 }

Browsing the AST

No can do?

Building a JSON AST

No can do?

Mapping to a case class

scala> case class Model(hello: String, age: Int) defined class Model scala> implicit val modelFormat = jsonFormat2(Model) modelFormat: spray.json.RootJsonFormat[Model] = spray.json.ProductFormatsInstances$$anon$2@7bc880f8 scala> res1.convertTo[Model] res4: Model = Model(world,42) 1 2 3 4 5 6 7 8 9 scala > case class Model ( hello : String , age : Int ) defined class Model scala > implicit val modelFormat = jsonFormat2 ( Model ) modelFormat : spray . json . RootJsonFormat [ Model ] = spray . json . ProductFormatsInstances $ $ anon $ 2 @ 7bc880f8 scala > res1 . convertTo [ Model ] res4 : Model = Model ( world , 42 )

spray-json-shapeless

spray-json-shapeless derives spray-json’s JsonFormat -s automatically using shapeless (no need to define an implicit JsonFormat

Mapping to a case class

scala> import spray.json._ import spray.json._ scala> import fommil.sjs.FamilyFormats._ import fommil.sjs.FamilyFormats._ scala> case class Model(hello: String, age: Int) defined class Model scala> Model("hello", 42).toJson res0: spray.json.JsValue = {"hello":"hello","age":42} 1 2 3 4 5 6 7 8 9 10 11 12 scala > import spray . json . _ import spray . json . _ scala > import fommil . sjs . FamilyFormats . _ import fommil . sjs . FamilyFormats . _ scala > case class Model ( hello : String , age : Int ) defined class Model scala > Model ( "hello" , 42 ) . toJson res0 : spray . json . JsValue = { "hello" : "hello" , "age" : 42 }

This is quite useful, it removes the boilerplate formats hanging around

json4s

json4s is a bit like slf4j in the sense that it tries to unite all kind of rogue libraries serving the same purpose by providing a common interface. But not every library uses it, which means that chances are high that your project will contain json4s in addition to another (few) Scala JSON libraries. Hopefully it will one day succeed with its slogan “One AST to rule them all”.

json4s has its roots in lift-json so this will look familiar:

Parsing raw JSON

scala> import org.json4s._ import org.json4s._ scala> import org.json4s.native.JsonMethods._ import org.json4s.native.JsonMethods._ scala> val rawJson = """{"hello": "world", "age": 42}""" rawJson: String = {"hello": "world", "age": 42} scala> parse(rawJson) res0: org.json4s.JValue = JObject(List((hello,JString(world)), (age,JInt(42)))) 1 2 3 4 5 6 7 8 9 10 11 12 scala > import org . json4s . _ import org . json4s . _ scala > import org . json4s . native . JsonMethods . _ import org . json4s . native . JsonMethods . _ scala > val rawJson = "" "{" hello ": " world ", " age ": 42}" "" rawJson : String = { "hello" : "world" , "age" : 42 } scala > parse ( rawJson ) res0 : org . json4s . JValue = JObject ( List ( ( hello , JString ( world ) ) , ( age , JInt ( 42 ) ) ) )

Browsing the AST

scala> res0 \ "hello" res1: org.json4s.JValue = JString(world) 1 2 3 scala > res0 \ "hello" res1 : org . json4s . JValue = JString ( world )

Building a JSON AST tree

scala> ("hello" -> "world") ~ ("age" -> 42) res2: org.json4s.JsonAST.JObject = JObject(List((hello,JString(world)), (age,JInt(42)))) 1 2 3 scala > ( "hello" -> "world" ) ~ ( "age" -> 42 ) res2 : org . json4s . JsonAST . JObject = JObject ( List ( ( hello , JString ( world ) ) , ( age , JInt ( 42 ) ) ) )

Mapping to a case class

object Json4sExample { def main(args: Array[String]): Unit = { import org.json4s._ import org.json4s.native.JsonMethods._ implicit val formats = DefaultFormats case class Model(hello: String, age: Int) val rawJson = """{"hello": "world", "age": 42}""" println(parse(rawJson).extract[Model]) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 object Json4sExample { def main ( args : Array [ String ] ) : Unit = { import org . json4s . _ import org . json4s . native . JsonMethods . _ implicit val formats = DefaultFormats case class Model ( hello : String , age : Int ) val rawJson = "" "{" hello ": " world ", " age ": 42}" "" println ( parse ( rawJson ) . extract [ Model ] ) } }

argonaut

Argonaut promotes “purely functional JSON in Scala”. It uses scalaz under the hood and

Parsing raw JSON

import scalaz._, Scalaz._ import scalaz._ import Scalaz._ scala> import argonaut._, Argonaut._ import argonaut._ import Argonaut._ scala> val rawJson = """{"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}""" rawJson: String = {"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}} scala> rawJson.parseOption res0: Option[argonaut.Json] = Some({"hello":"world","age":42,"nested":{"deeper":{"treasure":true}}}) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import scalaz . _ , Scalaz . _ import scalaz . _ import Scalaz . _ scala > import argonaut . _ , Argonaut . _ import argonaut . _ import Argonaut . _ scala > val rawJson = "" "{" hello ": " world ", " age ": 42, " nested ": { " deeper ": { " treasure ": true }}}" "" rawJson : String = { "hello" : "world" , "age" : 42 , "nested" : { "deeper" : { "treasure" : true } } } scala > rawJson . parseOption res0 : Option [ argonaut . Json ] = Some ( { "hello" : "world" , "age" : 42 , "nested" : { "deeper" : { "treasure" : true } } } )

Browsing the AST

There are several mechanisms available, let’s use a lense. Those are funky:

scala> val ohMyLens = jObjectPL >=> jsonObjectPL("nested") >=> jObjectPL >=> jsonObjectPL("deeper") >=> jObjectPL >=> jsonObjectPL("treasure") >=> jBoolPL ohMyLens: scalaz.PLensFamily[argonaut.Json,argonaut.Json,Boolean,Boolean] = scalaz.PLensFamilyFunctions$$anon$2@8c894ab scala> ohMyLens.get(res0.get) res1: Option[Boolean] = Some(true) 1 2 3 4 5 6 scala > val ohMyLens = jObjectPL >= > jsonObjectPL ( "nested" ) >= > jObjectPL >= > jsonObjectPL ( "deeper" ) >= > jObjectPL >= > jsonObjectPL ( "treasure" ) >= > jBoolPL ohMyLens : scalaz . PLensFamily [ argonaut . Json , argonaut . Json , Boolean , Boolean ] = scalaz . PLensFamilyFunctions $ $ anon $ 2 @ 8c894ab scala > ohMyLens . get ( res0 . get ) res1 : Option [ Boolean ] = Some ( true )

Building a JSON AST tree

scala> ("hello", jString("world")) ->: ("age", jNumber(42)) ->: jEmptyObject res2: argonaut.Json = {"age":42,"hello":"world"} 1 2 3 scala > ( "hello" , jString ( "world" ) ) -> : ( "age" , jNumber ( 42 ) ) -> : jEmptyObject res2 : argonaut . Json = { "age" : 42 , "hello" : "world" }

Mapping to a case class

scala> case class Model(hello: String, age: Int) defined class Model scala> implicit def ModelCodecJson: CodecJson[Model] = casecodec2(Model.apply, Model.unapply)("hello", "age") ModelCodecJson: argonaut.CodecJson[Model] scala> rawJson.decodeOption[Model] res3: Option[Model] = Some(Model(world,42)) 1 2 3 4 5 6 7 8 9 scala > case class Model ( hello : String , age : Int ) defined class Model scala > implicit def ModelCodecJson : CodecJson [ Model ] = casecodec2 ( Model . apply , Model . unapply ) ( "hello" , "age" ) ModelCodecJson : argonaut . CodecJson [ Model ] scala > rawJson . decodeOption [ Model ] res3 : Option [ Model ] = Some ( Model ( world , 42 ) )

circe

Circe is a fork of Argonaut that uses cats instead of scalaz and uses shapeless to generate codecs.

Parsing raw JSON

scala> import io.circe._, io.circe.generic.auto._, io.circe.parse._, io.circe.syntax._ import io.circe._ import io.circe.generic.auto._ import io.circe.parse._ import io.circe.syntax._ scala> val rawJson = """{"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}}""" rawJson: String = {"hello": "world", "age": 42, "nested": { "deeper": { "treasure": true }}} scala> parse(rawJson).getOrElse(Json.empty) res0: io.circe.Json = { "hello" : "world", "age" : 42, "nested" : { "deeper" : { "treasure" : true } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 scala > import io . circe . _ , io . circe . generic . auto . _ , io . circe . parse . _ , io . circe . syntax . _ import io . circe . _ import io . circe . generic . auto . _ import io . circe . parse . _ import io . circe . syntax . _ scala > val rawJson = "" "{" hello ": " world ", " age ": 42, " nested ": { " deeper ": { " treasure ": true }}}" "" rawJson : String = { "hello" : "world" , "age" : 42 , "nested" : { "deeper" : { "treasure" : true } } } scala > parse ( rawJson ) . getOrElse ( Json . empty ) res0 : io . circe . Json = { "hello" : "world" , "age" : 42 , "nested" : { "deeper" : { "treasure" : true } } }

Browsing the AST

So far there’s only support for cursors which let you move around the JSON tree and do changes if you would like, not for Lenses yet:

scala> val cursor = res0.cursor cursor: io.circe.Cursor = CJson({ "hello" : "world", "age" : 42, "nested" : { "deeper" : { "treasure" : true } } }) scala> for { | nested <- cursor.downField("nested") | deeper <- nested.downField("deeper") | treasure <- deeper.downField("treasure") | } yield treasure.as[Boolean] res1: Option[io.circe.Decoder.Result[Boolean]] = Some(Right(true)) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 scala > val cursor = res0 . cursor cursor : io . circe . Cursor = CJson ( { "hello" : "world" , "age" : 42 , "nested" : { "deeper" : { "treasure" : true } } } ) scala > for { | nested < - cursor . downField ( "nested" ) | deeper < - nested . downField ( "deeper" ) | treasure < - deeper . downField ( "treasure" ) | } yield treasure . as [ Boolean ] res1 : Option [ io . circe . Decoder . Result [ Boolean ] ] = Some ( Right ( true ) )

Mapping to a case class hierarchy

I didn’t get this to work with the example below. The example in the documentation works though so here is that one:

scala> sealed trait Foo defined trait Foo scala> case class Bar(xs: List[String]) extends Foo defined class Bar scala> case class Qux(i: Int, d: Option[Double]) extends Foo defined class Qux scala> val foo: Foo = Qux(13, Some(14.0)) foo: Foo = Qux(13,Some(14.0)) scala> foo.asJson.noSpaces res0: String = {"Qux":{"d":14.0,"i":13}} scala> decode[Foo](foo.asJson.spaces4) res1: cats.data.Xor[io.circe.Error,Foo] = Right(Qux(13,Some(14.0))) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 scala > sealed trait Foo defined trait Foo scala > case class Bar ( xs : List [ String ] ) extends Foo defined class Bar scala > case class Qux ( i : Int , d : Option [ Double ] ) extends Foo defined class Qux scala > val foo : Foo = Qux ( 13 , Some ( 14.0 ) ) foo : Foo = Qux ( 13 , Some ( 14.0 ) ) scala > foo . asJson . noSpaces res0 : String = { "Qux" : { "d" : 14.0 , "i" : 13 } } scala > decode [ Foo ] ( foo . asJson . spaces4 ) res1 : cats . data . Xor [ io . circe . Error , Foo ] = Right ( Qux ( 13 , Some ( 14.0 ) ) )

sphere-json

The sphere-json library focuses on providing an easy way to get de/serializers for entire families of case classes. This is really useful when working with any kind of protocol-related system. I am using it in a CQRS system where commands and events are travelling back and forth between the server and a Javascript UI. Instead of having to define a codec for each one of my case classes, I simply have them extend a common abstract class and let the library take care of the rest. Watch for yourselves:

Mapping to a case class hierarchy

import io.sphere.json.generic._ import io.sphere.json._ object Messages { sealed abstract class Message case class Hello(hello: String) extends Message case class Age(age: Int) extends Message } object SphereJsonExample { import Messages._ val rawHello = """{ "hello": "world", "type": "Hello" }""" val rawAge = """{ "age": 42, "type": "Age" }""" implicit val allYourJson = deriveJSON[Message] def magic(json: String) = fromJSON[Message](json).fold( fail => println("Oh noes: " + fail), hi => println(hi) ) def main(args: Array[String]): Unit = { magic(rawHello) magic(rawAge) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import io . sphere . json . generic . _ import io . sphere . json . _ object Messages { sealed abstract class Message case class Hello ( hello : String ) extends Message case class Age ( age : Int ) extends Message } object SphereJsonExample { import Messages . _ val rawHello = "" "{ " hello ": " world ", " type ": " Hello " }" "" val rawAge = "" "{ " age ": 42, " type ": " Age " }" "" implicit val allYourJson = deriveJSON [ Message ] def magic ( json : String ) = fromJSON [ Message ] ( json ) . fold ( fail = > println ( "Oh noes: " + fail ) , hi = > println ( hi ) ) def main ( args : Array [ String ] ) : Unit = { magic ( rawHello ) magic ( rawAge ) } }

jawn

jawn is a library that focuses on speed. It defines a lightweight AST and is compatible with all kind of other ASTs that we have seen here.

Parsing raw JSON

scala> import jawn._ import jawn._ scala> import jawn.ast._ import jawn.ast._ scala> val rawJson = """{"hello": "world", "age": 42}""" rawJson: String = {"hello": "world", "age": 42} scala> jawn.ast.JParser.parseFromString(rawJson).get res0: jawn.ast.JValue = {"age":42,"hello":"world"} 1 2 3 4 5 6 7 8 9 10 11 12 scala > import jawn . _ import jawn . _ scala > import jawn . ast . _ import jawn . ast . _ scala > val rawJson = "" "{" hello ": " world ", " age ": 42}" "" rawJson : String = { "hello" : "world" , "age" : 42 } scala > jawn . ast . JParser . parseFromString ( rawJson ) . get res0 : jawn . ast . JValue = { "age" : 42 , "hello" : "world" }

rapture

rapture’s json library is the ultimate Scala JSON library. It doesn’t really do anything with JSON itself, instead, it abstracts over the following JSON libraries (which it calls backends ):

Argonaut

Jackson

Jawn

JSON4S

Lift

Play

Scala standard library JSON

Spray

Parsing raw JSON

scala> import rapture.json._ scala> import rapture.json.jsonBackends.play._ import rapture.json.jsonBackends.play._ scala> Json.parse(rawJson) res2: rapture.json.Json = {"hello":"world","age":42} 1 2 3 4 5 6 7 8 scala > import rapture . json . _ scala > import rapture . json . jsonBackends . play . _ import rapture . json . jsonBackends . play . _ scala > Json . parse ( rawJson ) res2 : rapture . json . Json = { "hello" : "world" , "age" : 42 }

Browsing the AST

Rapture is using Scala’s Dynamic trait, which makes this fun:

scala> res2.hello.as[String] res3: String = world 1 2 3 scala > res2 . hello . as [ String ] res3 : String = world

Building a JSON AST tree

scala> json"""{ "hello": "world", "age": 42}""" res6: rapture.json.Json = {"hello":"world","age":42} 1 2 3 scala > json "" "{ " hello ": " world ", " age ": 42}" "" res6 : rapture . json . Json = { "hello" : "world" , "age" : 42 }

The Scala standard library

Up until recently I did not know that Scala had a JSON utility in its standard library. But here it is!

Parsing raw JSON

scala> import scala.util.parsing.json._ import scala.util.parsing.json._ scala> val rawJson = """{"hello": "world", "age": 42}""" rawJson: String = {"hello": "world", "age": 42} scala> JSON.parseFull(rawJson) warning: there was one deprecation warning; re-run with -deprecation for details res0: Option[Any] = Some(Map(hello -> world, age -> 42.0)) 1 2 3 4 5 6 7 8 9 10 scala > import scala . util . parsing . json . _ import scala . util . parsing . json . _ scala > val rawJson = "" "{" hello ": " world ", " age ": 42}" "" rawJson : String = { "hello" : "world" , "age" : 42 } scala > JSON . parseFull ( rawJson ) warning : there was one deprecation warning ; re - run with - deprecation for details res0 : Option [ Any ] = Some ( Map ( hello -> world , age -> 42.0 ) )

Browsing the “AST”

scala> res0.get.asInstanceOf[Map[String, Any]]("hello").asInstanceOf[String] res4: String = world 1 2 3 scala > res0 . get . asInstanceOf [ Map [ String , Any ] ] ( "hello" ) . asInstanceOf [ String ] res4 : String = world

Even more!

I am forgetting a ton of libraries here I am sure. That, and I am tired and my glass of Uigeadail is getting empty.

So let me mention a few more:

Julien Richard-Foy built the play-json-variants which add the root hierarchy capability to play-json

Pascal Voitot added a few functional manipulations to play-json in play-json-zipper

Robert J. Macomber built the rojoma-json library which I only discovered now and am too tired to cover (sorry)

Great, now which one to pick?

Honestly I don’t have any good advice here. These days I am sticking to play-json and sphere-json, but that’s for no particular reason other than these libraries being there and doing what I need to do (parse and write JSON, traverse and some rare times transform the AST, bind to case classes, make it possible to support custom Java types). If play-json had support for hierarchies out of the box I would probably not have even looked for anything else.

Because for all the joy there seems to be in implementing JSON libraries in Scala, one thing has to be said: JSON de/serialization is boring. It’s this annoying thing that you have to do in order to get your application to talk to another computerized system, period.

I have never met a developer who told me how much pleasure they derived from turning classes into JSON strings and back. That’s just not a thing.

I have, however, met more than one developer that has run into trouble getting library X to cover one of the simple use-cases outlined above. Believe me, there is nothing more frustrating than having to spend time on the annoying task of setting up JSON de/serialization in order to do the boring thing of tossing strings back and forth the network. That’s time you will never get back.

Good night, and good luck.