SON of JSON II

SON of JSON growing up

In my previous post, two days ago, I mentioned that I got sick of having to deal with all the complexity of json4s and wanted something that's easier to use, even though that might come at the expense of a lot of cleverness, bells, whistles, poneys and rainbows.

Just to summarize: I think Scala needs something that is way more intuitive than what json4s is offering, easier on the eyes, easier to handle, and ideally it shouldn't be too different from the way JavaScript allows you to deal with it.

At first I started off writing it on top of json4s. However, two reasons made me reconsider that. ① First of all, I had my wisdom teeth getting removed, preventing me from sleeping for a couple of days, and as a result, I had a lot of spare time on my hands. ② Secondly, I started working on setters. And I wanted to be able to modify data in Scala as you would do it in JavaScript, so like this:

person.name.first = "John"

If you would do that using json4s, you would actually have to replace the entire object, and not just the name, as a result of the immutable nature of json4s. But that would also mean that - if I would already have pointers to person.name - that version of the name would not get updated as a result. All in all, it would just be too weird to be of any use. So I needed a mutable version of a JSON object model.

When it turned out that nothing like that existed, it meant I had to write my own, and as a consequence also my own parser and renderer. But that way, it actually would no longer be all that sensible to have the json4s dependency. So therefore, behold, the new version of SON of JSON does no longer depend on json4s. It has its own JSON parser, it's own renderer, and it's own object model. And that object model is mutable, for reasons I just outlined.

So what I will do below is give you a new and updated overview of the library, and add all the new features it has.

Let's stick to the example of the previous post, and continue working on object hat would be defined like this in JSON:

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

Constructing it in Scala

First of all, let's look on how you would construct these objects, both in json4s and in sonofjson.

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

Constructing it with DSLs?

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 how sonofjson is doing it.

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

Getting the data out

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]
// Or
val name: String = person.name.first

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

Parsing

Now the above is already quite powerful, but in reality, in many cases, you want to be able to parse JSON from some other source, and not construct it manually by hand.

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" }""")

Updating the object

There's one thing we haven't considered yet, and that's one of the things I added today, and made me drop json4s as a dependency. It's updating the object. It's awesome that you can parse and inspect an object in an easy way, and that you can piece new objects together the easy way, but you also want to be able to modify data. In json4s, modifying the data essentially means creating a new object. Great for threadsafety, not so great for ease of use. SON of JSON is taking another route: objects and arrays are mutable, which makes it much more similar to the way you would handle it in JavaScript:

Vanilla json4s Vanilla sonofjson
import org.json4s._

val person = …

person merge JObject(
  "name" -> JObject(
    "first" -> JString("Jack")
  )
)
import nl.typeset.sonofjson._

val person = …

person.name.first = "Jack"

I hope you like it so far. I wouldn't exactly consider SON of JSON to be ready for production yet, but I'd definitely encourage you to give it a try and let me know how you think about it.