SON of JSON

JSON4S UX Fairy dust

I'll admit, I always feel a little embarrassed when I need to explain to people how to deal with JSON in Scala. I mean, sure, there is a great library that is superfast, but really, all the imports you need to do, implicits that are floating around and the weird notation, it just doesn't help to convince that Scala is the right tool for the job. In all honesty, I think it actually fails quite a few tests from Joshua Bloch's Bumper-Sticker API Design talk, including:

  • APIs should be easy to use and hard to misuse
  • APIs should be self-documenting
  • Don't make the client do anything the library could do
  • Obey the principle of least astonishment

I don't like feeling embarrassed about the code I'm writing. Hence, I started writing my own JSON library. Behold, I present to you: "SON of JSON". It is built on top of json4s, which makes a whole bunch of things a lot easier, but from my humble point of view, it does feel a little easier to use.

As an example, let's just say we want something capturing this JSON document:

    {
    	"name" : {
    		"first" : "John",
    		"last" : "Doe"
    	},
    	"age" : 41,
    	"numbers"  : [5, 4, 3]
   }

Then this is how you deal with it in both libraries:

Vanilla json4s Vanilla sonofjson
import org.json4s._

val person = JObject(
  "name" -> JObject(
    "first" -> "John",
    "last" -> "Doe"
  ),
  "age" -> 41,
  "numbers" -> JArray(List(
    JInt(5), JInt(4), JInt(3)
  ))
}
import nl.typeset.sonofjson._

val person = obj(
  name = obj(
    first = "John",
    last = "Doe"
  ),
  age = 41,
  numbers = arr(5, 4, 3)
)

Now you will probably go: hey, but isn't there a DSL for constructing objects in json4s as well? You're right, let's compare that one with sonofjson as well.

json4s DSL Vanilla sonofjson
import org.json4s._
import JsonDSL._

val person = 
  ("name" ->  (
    ("first" -> "John") ~
    ("last" -> "Doe")
  ) ~
  ("age" -> 41) ~
  ("numbers" -> List(5, 4, 3))
import nl.typeset.sonofjson._

val person = obj(
  name = obj(
    first = "John",
    last = "Doe"
  ),
  age = 41,
  numbers = arr(5, 4, 3)
)

So now we have the data stored in a variable. How about getting some of it out again? Here's how you do it both libraries. Now, the truth is that in json4s there are actually multiple ways to get the data out - which is part of the problem - but I'll focus on the most common way right now.

Vanilla json4s Vanilla sonofjson
import org.json4s._
import org.json4s.native.JsonMethods._

val person = …
implicit val formats = DefaultFormats

// First name
(person \ "name" \ "first").extract[String] 

// First number
(person \ "numbers")(0).extract[Int] 
import nl.typeset.sonofjson._

val person =  …

// First name
person.name.first.as[String]

// First number
person.numbers(0).as[Int]

So how about parsing?

Vanilla json4s Vanilla sonofjson
import org.json4s._
import org.json4s.native.JsonMethods._

val json = parse("""{ "foo" : "bar" }""")
import nl.typeset.sonofjson._

val json = parse("""{ "foo" : "bar" }""")

Update Friday 19 September 2014:

Working on setters now. This is working, but the issue will be nested objects and lists.

val person = obj(
  first = "John",
  last = "Doe"
)
person.first = "Jack"
assert(person.first == "Jack")