More than you wished for

Stop the JSON madness

It will not come as a surprise that I like Scala. Functions are first class citizens. Great. You can write APIs that then seem to seamlessly blend in with the language. Love it. You don't have to declare types when it really to doesn't make any sense. Nice.

That doesn't mean I find it the perfect language for everything. If I would have had it my way, then Scala's syntax would have been based on whitespace indent rather than curly braces, for instance. I love Jade, CoffeeScript and HAML. Using the curly braces is starting to feel wrong.

But the thing that bugs me most about Scala is that its support for JSON feels clumsy. Don't get me wrong: I think the libraries are pretty powerful, but having to type this:

import net.liftweb.json.JsonDSL._

val json = 
("name" ->
  ("first" -> "wilfred") ~
  ("last" -> "springer")
)

Instead of this:

val json = { 
  "name" : {
    "first": "wilfred",
    "last": "springer"
  }
}

… just feels wrong.

Anyway, matters could have been worse, I guess. At least lift-json (the library used above) makes it bearable. Not fun, but bearable.

Unfortunately, lift-json is not the only library around. There are quite a few more. And that's one of the other disadvantages of Scala. Everything has to be reinvented, and many people are reinventing the same thing simultaneously. That means you look at one solution one day, decide to adopt it, but you really want to use something completely else the other day.

On a project we started three years ago, I decided to use sjson, which supports a typeclass based type of serialization. Sjson itself is bolted on top of dispatch-json. In order to make it work, you need to provide some directions using implicit values implementing a Format interface. It works fairly well.

However in another part of the project, we had to serialize the same data types using lift-json. Lift-json is able to support case classes by default, but for anything else than the default mapping, it need to provide a Serializer, which is to some extent similar to sjson's Format objects.

You can already sense where this is going. In the end, every case class required both a Format as well as a Serializer. It's duplication I'd rather do without.

In a first stab to make this easier to handle, I wrote some utility functions to transform between lift-json and dispatch-json. You might find it useful:

import dispatch.json._
import net.liftweb.json.JsonAST._
import net.liftweb.json.JsonAST.JDouble
import dispatch.json.JsArray
import net.liftweb.json.JsonAST.JField
import net.liftweb.json.JsonAST.JBool
import net.liftweb.json.JsonAST.JObject
import net.liftweb.json.JsonAST.JString
import dispatch.json.JsBoolean
import net.liftweb.json.JsonAST.JInt
import net.liftweb.json.JsonAST.JArray

package object compat {

  def liftToDispatch(in: JValue): JsValue = in match {
    case JObject(fields) =>
      JsObject(for (JField(name, value) <- fields) yield
        JsString(name) -> liftToDispatch(value))
    case JField(name, value) => JsString(name, liftToDispatch(value))
    case JString(str) => JsString(str)
    case JDouble(num) => JsNumber(num)
    case JInt(num) => JsNumber(num)
    case JNothing => JsNull
    case JArray(values) => JsArray(values.map(liftToDispatch))
    case JBool(value) => value match {
      case true => JsTrue
      case false => JsFalse
    }
    case JNull => JsNull
  }

  def dispatchToLift(in: JsValue): JValue = in match {
    case JsObject(fields) =>
      JObject(for ((JsString(key), value) <- fields.toList) yield 
        JField(key, dispatchToLift(value)))
    case JsNumber(value) => 
      if (value.scale > 0) JDouble(value.toDouble) 
      else JInt(value.toBigInt())
    case JsBoolean(value) => JBool(value)
    case JsString(value) => JString(value)
    case JsNull => JNull
    case JsArray(values) =>
      JArray(values.map(dispatchToLift))
  }

}

I guess it should be possible to implicitly convert from lift-json's Serializers to sjson Formats, but I haven't tried that and haven't had the need for it yet. If anyone ever tries it, I'd love to know about it though.