Sunday, 24 March 2013

The simplest dynamically typed json parsing with Dynamic in Scala 2.10

I love the way dynamically typed languages handle json or xml parsing. With Scala 2.10 we can do it using Dynamic trait. Here is a first draft.
Please feel free to copy/paste/reuse at your own risk.
import util.parsing.json.JSON
import io.Source
import scala.language.dynamics
object Example extends App{
val json = """{
"name" : "Adam Slodowy",
"active": "true",
"roles" : [ "teacher", "admin" ],
"lessons" : [ { "id": 1 }, { "id": 2 } ],
"security" : { "id" : 123, "login": "adams" }
}"""
val adam = JsonElement.parse(json).get
case class Lesson(teacher: String, id: Int, name: String, active : Boolean = true)
val lesson = Lesson(adam.name, adam.lessons.at(1).id, adam.lessons.at(1).name, adam.active)
// will create Lesson("Adam Slodowy", 2, "", true ) - see the implicit conversions
}
trait JsonElement extends Dynamic{ self =>
def selectDynamic(field: String) : JsonElement = EmptyElement
def applyDynamic(field: String)(i: Int) : JsonElement = EmptyElement
def toList : List[String] = sys.error(s"$this is not a list.")
def asString: String = sys.error(s"$this has no string representation.")
def length$ : Int = sys.error(s"$this has no length")
}
object JsonElement{
def ^(s: String) = {
require(!s.isEmpty, "Element is empty")
s
}
implicit def toString(e: JsonElement) : String = e.asString
implicit def toBoolean(e: JsonElement) : Boolean = (^(e.asString)).toBoolean
implicit def toBigDecimal(e: JsonElement) : BigDecimal = BigDecimal(^(e.asString))
implicit def toDouble(e: JsonElement) : Double = ^(e.asString).toDouble
implicit def toFloat(e: JsonElement) : Float = ^(e.asString).toFloat
implicit def toByte(e: JsonElement) : Byte = ^(e.asString).stripSuffix(".0").toByte
implicit def toShort(e: JsonElement) : Short = ^(e.asString).stripSuffix(".0").toShort
implicit def toInt(e: JsonElement) : Int = ^(e.asString).stripSuffix(".0").toInt
implicit def toLong(e: JsonElement) : Long = ^(e.asString).stripSuffix(".0").toLong
implicit def toList(e: JsonElement) : List[String] = e.toList
def parse(json: String) = JSON.parseFull(json) map (JsonElement(_))
def apply(any : Any) : JsonElement = any match {
case x : Seq[Any] => new ArrayElement(x)
case x : Map[String, Any] => new ComplexElement(x)
case x => new PrimitiveElement(x)
}
}
case class PrimitiveElement(x: Any) extends JsonElement{
override def asString = x.toString
}
case object EmptyElement extends JsonElement{
override def asString = ""
override def toList = Nil
}
case class ArrayElement(private val x: Seq[Any]) extends JsonElement{
private lazy val elements = x.map((JsonElement(_))).toArray
override def applyDynamic(field: String)(i: Int) : JsonElement = elements.lift(i).getOrElse(EmptyElement)
override def toList : List[String] = elements map (_.asString) toList
override def length$ : Int = elements.length
}
case class ComplexElement(private val fields : Map[String, Any]) extends JsonElement{
override def selectDynamic(field: String) : JsonElement = fields.get(field) map(JsonElement(_)) getOrElse(EmptyElement)
}
view raw Json.scala hosted with ❤ by GitHub

1 comment:

  1. I wrote new library [dijon](https://github.com/pathikrit/dijon) - it written purely in less than 100 lines of dependency free Scala. It uses Scala dynamic types too e.g.

    val age = 9
    val rick = json"""{ "name": "rick", "age": $age}"""
    assert(rick.age == 9)
    rick.age = if (rick.age.as[Double].get > 18) "adult" else "youth"
    rick.address = `{}`
    rick.address.city = "Seattle"
    rick.address.isMain = true
    assert(rick == json"""{ "name": "rick", "age": "youth", "address": {"city": "Seattle", "isMain": true}}""")

    ReplyDelete