Permalink
Browse files

First pass at JsonTraverser

  • Loading branch information...
jorgeortiz85 committed Feb 21, 2012
0 parents commit 17d49f19964ecec0f83114d6e22c6b5804cf449e
@@ -0,0 +1,7 @@
+libraryDependencies ++= Seq(
+ "net.liftweb" %% "lift-json" % "2.4-M5" % "compile" withSources())
+
+scalacOptions ++= Seq(
+ "-deprecation",
+ "-unchecked",
+ "-Xexperimental")
@@ -0,0 +1,37 @@
+// package com.foursquare.json.traverser
+//
+// import net.liftweb.json.JsonAST._
+//
+// case class DynamicJsonTraverser(getTraverser: JsonTraverser) extends JsonTraverser with Dynamic {
+// def this(json: JValue) =
+// this(new JsonTraverserImpl(json))
+//
+// override def getJson: JValue =
+// getTraverser.getJson
+//
+// override def getError: Option[JsonError] =
+// getTraverser.getError
+//
+// override def getResult[T: ResultType]: Option[T] =
+// getTraverser.getResult[T]
+//
+// override def getIndex(index: Int): DynamicJsonTraverser =
+// DynamicJsonTraverser(getTraverser.getIndex(index))
+//
+// override def getKey(name: String): DynamicJsonTraverser =
+// DynamicJsonTraverser(getTraverser.getKey(name))
+//
+// def apply(index: Int): DynamicJsonTraverser =
+// getIndex(index)
+//
+// def applyDynamic(name: String)(indexes: Int*): DynamicJsonTraverser = {
+// if (indexes.isEmpty)
+// getKey(name)
+// else
+// getKey(name).getIndex(indexes.head)
+// }
+// }
+//
+// object DynamicJsonTraverser {
+// def apply(json: JValue): DynamicJsonTraverser = new DynamicJsonTraverser(json)
+// }
@@ -0,0 +1,5 @@
+package com.foursquare.json.traverser
+
+import net.liftweb.json.JValue
+
+case class JsonError(reason: String, json: JValue)
@@ -0,0 +1,21 @@
+package com.foursquare.json.traverser
+
+import net.liftweb.json.JsonAST.JValue
+
+trait JsonTraverser {
+ def getJson: JValue
+ def getError: Option[JsonError]
+ def getResult[T: ResultType]: Option[T] = {
+ val resultType = implicitly[ResultType[T]]
+ resultType.parse(getJson)
+ }
+
+ // def getIndex(index: Int): JsonTraverser
+ // def getKey(name: String): JsonTraverser
+ def apply(index: Int): JsonTraverser
+ def apply(name: String): JsonTraverser
+}
+
+object JsonTraverser {
+ def apply(json: JValue): JsonTraverser = new JsonTraverserImpl(json)
+}
@@ -0,0 +1,15 @@
+package com.foursquare.json.traverser
+
+import net.liftweb.json.JsonAST.{JValue, JNothing}
+
+private case class JsonTraverserErrorImpl(override val getError: Option[JsonError]) extends JsonTraverser {
+ override def getJson: JValue = JNothing
+
+ // We propagate only the first error
+ override def apply(index: Int): JsonTraverser = this
+
+ // We propagate only the first error
+ override def apply(name: String): JsonTraverser = this
+
+ override def getResult[T: ResultType]: Option[T] = None
+}
@@ -0,0 +1,35 @@
+package com.foursquare.json.traverser
+
+import net.liftweb.json.JsonAST.{JValue, JArray, JNothing}
+
+private case class JsonTraverserImpl(override val getJson: JValue) extends JsonTraverser {
+ override def getError: Option[JsonError] = None
+
+ override def apply(index: Int): JsonTraverser = getJson match {
+ case JArray(arr) if arr.size > index =>
+ new JsonTraverserImpl(arr(index))
+
+ case JArray(arr) =>
+ val msg = "Attempt to access index " + index + " in array of length " + arr.size
+ val error = JsonError(msg, getJson)
+ new JsonTraverserErrorImpl(Some(error))
+
+ case _ =>
+ val error = JsonError("Json value is not an array", getJson)
+ new JsonTraverserErrorImpl(Some(error))
+ }
+
+ override def apply(name: String): JsonTraverser = {
+ val newJson = getJson \ name
+
+ newJson match {
+ case JNothing =>
+ val error = JsonError("Attempt to access nonexistant field: " + name, getJson)
+ new JsonTraverserErrorImpl(Some(error))
+
+ case _ =>
+ new JsonTraverserImpl(newJson)
+ }
+ }
+}
+
@@ -0,0 +1,45 @@
+package com.foursquare.json.traverser
+
+import net.liftweb.json.JsonAST._
+
+sealed class ResultType[T](pf: PartialFunction[JValue, T]) {
+ val parse = pf.lift
+}
+
+object ResultType {
+ implicit object String extends ResultType[String]({
+ case JString(str) => str
+ })
+
+ implicit object Int extends ResultType[Int]({
+ case JInt(bigInt) if bigInt.isValidInt => bigInt.intValue
+ })
+
+ implicit object Long extends ResultType[Long]({
+ case JInt(bigInt) => bigInt.longValue
+ })
+
+ implicit object BigInt extends ResultType[BigInt]({
+ case JInt(bigInt) => bigInt
+ })
+
+ implicit object Boolean extends ResultType[Boolean]({
+ case JBool(bool) => bool
+ })
+
+ implicit object Null extends ResultType[Null]({
+ case JNull => null
+ })
+
+ implicit object Double extends ResultType[Double]({
+ case JDouble(n) => n
+ })
+
+ implicit object JObject extends ResultType[JObject]({
+ case jobj: JObject => jobj
+ })
+
+ implicit object JArray extends ResultType[JArray]({
+ case jarr: JArray => jarr
+ })
+}
@@ -0,0 +1,40 @@
+import com.foursquare.json.traverser.JsonTraverser //, DynamicJsonTraverser}
+import net.liftweb.json.JsonParser
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ val jsonStr = """{"id_str":"171331379555090432","coordinates":{"coordinates":[-73.77090964,40.75205260],"type":"Point"},"in_reply_to_status_id_str":"171331036926586883","place":{"name":"Queens","attributes":{},"full_name":"Queens, NY","place_type":"city","country":"United States","bounding_box":{"coordinates":[[[-74.042112,40.489794],[-73.700272,40.489794],[-73.700272,40.812242],[-74.042112,40.812242]]],"type":"Polygon"},"id":"b6ea2e341ba4356f","url":"http:\/\/api.twitter.com\/1\/geo\/id\/b6ea2e341ba4356f.json","country_code":"US"},"geo":{"coordinates":[40.75205260,-73.77090964],"type":"Point"},"in_reply_to_user_id_str":"3187821","favorited":false,"created_at":"Sun Feb 19 20:32:38 +0000 2012","user":{"id":3032241,"profile_background_image_url_https":"https:\/\/si0.twimg.com\/profile_background_images\/4689671\/bg.png","created_at":"Sat Mar 31 02:11:20 +0000 2007","profile_image_url":"http:\/\/a3.twimg.com\/profile_images\/1776174244\/image1327348450_normal.png","profile_text_color":"663B12","time_zone":"Eastern Time (US & Canada)","following":true,"followers_count":1327,"profile_sidebar_border_color":"C6E2EE","screen_name":"bdotdub","verified":false,"utc_offset":-18000,"default_profile_image":false,"location":"New York, NY","name":"Benny Wong","notifications":false,"profile_background_tile":true,"favourites_count":207,"id_str":"3032241","profile_sidebar_fill_color":"c2e4a0","contributors_enabled":false,"protected":false,"show_all_inline_media":true,"profile_image_url_https":"https:\/\/si0.twimg.com\/profile_images\/1776174244\/image1327348450_normal.png","profile_background_color":"c4edc8","description":"building time machine as cofounder of @timehop) w\/ @jwegener \u2022 built @ExitStrategyNYC iPhone app \u2022 formerly built @GiltCity","default_profile":false,"lang":"en","is_translator":false,"profile_background_image_url":"http:\/\/a0.twimg.com\/profile_background_images\/4689671\/bg.png","statuses_count":8596,"geo_enabled":true,"profile_link_color":"18b500","follow_request_sent":false,"friends_count":546,"listed_count":84,"profile_use_background_image":true,"url":"http:\/\/bwong.net"},"in_reply_to_screen_name":"jwegener","in_reply_to_user_id":3187821,"retweeted":false,"in_reply_to_status_id":171331036926586883,"contributors":null,"truncated":false,"source":"\u003Ca href=\"http:\/\/twitter.com\/#!\/download\/iphone\" rel=\"nofollow\"\u003ETwitter for iPhone\u003C\/a\u003E","id":171331379555090432,"retweet_count":0,"text":"@jwegener @msingleton @ericfriedman agrizzle. Eric was building subscribetoit for @msg"}"""
+ val json = JsonParser.parse(jsonStr)
+
+ // Using Lift extractors
+ implicit val formats = net.liftweb.json.DefaultFormats
+ val coords1 = json \ "geo" \ "coordinates"
+ val xy1 =
+ try {
+ val x = coords1.children(0).extract[Double]
+ val y = coords1.children(1).extract[Double]
+ Some((x, y))
+ } catch {
+ case ex: Exception => None
+ }
+
+ // Using JsonTraverser
+ val tjson = JsonTraverser(json)
+ val coords2 = tjson("geo")("coordinates")
+
+ val xy2 =
+ for {
+ x <- coords2(0).getResult[Double]
+ y <- coords2(1).getResult[Double]
+ } yield (x, y)
+
+ // Using DynamicJsonTraverser
+ // val djson = DynamicJsonTraverser(json)
+ // val coords3 = djson.geo.coordinates
+ // val xy3 =
+ // for {
+ // x <- coords3(0).getResult[Double]
+ // y <- coords3(1).getResult[Double]
+ // } yield (x, y)
+ }
+}

0 comments on commit 17d49f1

Please sign in to comment.