Skip to content

Instantly share code, notes, and snippets.

@piotrga
Last active April 4, 2018 07:42
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save piotrga/5233317 to your computer and use it in GitHub Desktop.
Save piotrga/5233317 to your computer and use it in GitHub Desktop.
The simplest dynamically typed json parsing with Dynamic in Scala 2.10
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)
}
@danistrebel
Copy link

Thanks a lot for your nice parser, was really helpful to me!
Just a small hint: In order to parse ArrayElements the type of the method should probably be List[JsonElement]

@pathikrit
Copy link

Try this new library dijon - it written purely in less than 100 lines of dependency free Scala. It uses Scala dynamic types 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}}""")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment