Permalink
Browse files

Initial json module. Add production files and pom.

  • Loading branch information...
1 parent 215ca9f commit eca4bf99b807b05de600fe1ad454153c0b6477a5 Joni Freeman committed Aug 11, 2009
View
@@ -0,0 +1,31 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>lift</artifactId>
+ <groupId>net.liftweb</groupId>
+ <version>1.1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <artifactId>lift-json</artifactId>
+ <!-- <packaging>jar</packaging> -->
+ <name>Lift Json</name>
+ <description>Json utilities</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-tools.testing</groupId>
+ <artifactId>specs</artifactId>
+ <version>1.5.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.5</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
@@ -0,0 +1,91 @@
+package net.liftweb.json
+
+import scala.reflect.Manifest
+import JsonAST._
+
+/** Function to extract values from JSON AST using case classes.
+ * Note, this is experimental and not fully implemented yet.
+ *
+ * See: example.scala "Extraction example", "Partial extraction example"
+ */
+object Extraction {
+ /** Intermediate format which describes the mapping.
+ * This ADT is constructed (and then memoized) from given case class using reflection.
+ *
+ * Example mapping.
+ *
+ * package xx
+ * case class Person(name: String, address: Address, children: List[Child])
+ * case class Address(street: String, city: String)
+ * case class Child(name: String, age: BigInt)
+ *
+ * will produce following Mapping:
+ *
+ * Constructor(None, "xx.Person", List(
+ * Value("name"),
+ * Constructor(Some("address"), "xx.Address", List(Value("street"), Value("city"))),
+ * ListConstructor("children", "xx.Child", List(Value("name"), Value("age")))))
+ */
+ sealed abstract class Mapping
+ case class Value(path: String) extends Mapping
+ case class Constructor(path: Option[String], classname: String, args: List[Mapping]) extends Mapping
+ case class ListConstructor(path: String, classname: String, args: List[Mapping]) extends Mapping
+
+ // FIXME; should the return type be Either[MappingError, A] ?
+ def extract[A](json: JValue)(implicit mf: Manifest[A]) = {
+ val mapping = memoize(mf.erasure)
+
+ def newInstance(classname: String, args: List[Any]) = {
+ val clazz = Class.forName(classname)
+ val argTypes = args.map {
+ case x: List[_] => classOf[List[_]]
+ case x => x.asInstanceOf[AnyRef].getClass
+ }
+ clazz.getConstructor(argTypes.toArray: _*).newInstance(args.map(_.asInstanceOf[AnyRef]).toArray: _*)
+ }
+
+ def build(root: JValue, mapping: Mapping, argStack: List[Any]): List[Any] = mapping match {
+ case Value(path) => fieldValue(root, path).values :: argStack
+ case Constructor(path, classname, args) =>
+ val newRoot = path match {
+ case Some(p) => root \ p
+ case None => root
+ }
+ newInstance(classname, args.flatMap(build(newRoot, _, argStack))) :: Nil
+ case ListConstructor(path, classname, args) =>
+ val arr = fieldValue(root, path).asInstanceOf[JArray]
+ arr.arr.map(elem => newInstance(classname, args.flatMap(build(elem, _, argStack)))) :: argStack
+ }
+
+ def fieldValue(json: JValue, path: String) = (json \ path).asInstanceOf[JField].value
+
+ build(json, mapping, Nil).head
+ }
+
+ // FIXME memoize
+ private def memoize(clazz: Class[_]) = {
+ def makeMapping(path: Option[String], clazz: Class[_], isList: Boolean): Mapping = isList match {
+ case false => Constructor(path, clazz.getName, constructorArgs(clazz))
+ case true => ListConstructor(path.get, clazz.getName, constructorArgs(clazz))
+ }
+
+ // FIXME add rest of the primitives
+ def constructorArgs(clazz: Class[_]) = clazz.getDeclaredFields.map { x =>
+ if (x.getType == classOf[String]) Value(x.getName)
+ else if (x.getType == classOf[BigInt]) Value(x.getName)
+ else if (x.getType == classOf[List[_]]) makeMapping(Some(x.getName), Util.parametrizedType(x), true)
+ else makeMapping(Some(x.getName), x.getType, false)
+ }.toList.reverse // FIXME Java6 returns these in reverse order, verify that and check other vms
+
+ makeMapping(None, clazz, false)
+ }
+}
+
+object Util {
+ import java.lang.reflect._
+
+ def parametrizedType(f: Field): Class[_] = {
+ val ptype = f.getGenericType.asInstanceOf[ParameterizedType]
+ ptype.getActualTypeArguments()(0).asInstanceOf[Class[_]]
+ }
+}
@@ -0,0 +1,215 @@
+package net.liftweb.json
+
+object JsonAST {
+ import scala.text.Document
+ import scala.text.Document._
+
+ sealed abstract class JValue {
+ type Values
+
+ def \(nameToFind: String): JValue = {
+ def find(xs: List[JValue]): List[JValue] = xs.flatMap {
+ case JObject(l) => l.filter {
+ case JField(name, value) if name == nameToFind => true
+ case _ => false
+ }
+ case JArray(l) => find(l)
+ case field @ JField(name, value) if name == nameToFind => field :: Nil
+ case _ => Nil
+ }
+ find(children) match {
+ case Nil => JNothing
+ case x :: Nil => x
+ case x => JArray(x)
+ }
+ }
+
+ // FIXME this must be tail recursive
+ def \\(nameToFind: String): JObject = {
+ def find(json: JValue): List[JField] = json match {
+ case JObject(l) => l.foldLeft(List[JField]())((a, e) => a ::: find(e))
+ case JArray(l) => l.foldLeft(List[JField]())((a, e) => a ::: find(e))
+ case field @ JField(name, value) if name == nameToFind => field :: find(value)
+ case JField(_, value) => find(value)
+ case _ => Nil
+ }
+ JObject(find(this))
+ }
+
+ def apply(i: Int): JValue = JNothing
+
+ def values: Values
+
+ def children = this match {
+ case JObject(l) => l
+ case JArray(l) => l
+ case JField(n, v) => List(v)
+ case _ => Nil
+ }
+
+ def find(p: JValue => Boolean): Option[JValue] = {
+ def find(json: JValue): Option[JValue] = {
+ if (p(json)) return Some(json)
+ json match {
+ case JObject(l) => l.flatMap(find _).firstOption
+ case JArray(l) => l.flatMap(find _).firstOption
+ case JField(_, value) => find(value)
+ case _ => None
+ }
+ }
+ find(this)
+ }
+
+ def filter(p: JValue => Boolean): List[JValue] = {
+ def filter(json: JValue, acc: List[JValue]): List[JValue] = {
+ val newAcc = if (p(json)) json :: acc else acc
+ json match {
+ case JObject(l) => l.foldLeft(newAcc)((a, e) => filter(e, a))
+ case JArray(l) => l.foldLeft(newAcc)((a, e) => filter(e, a))
+ case JField(_, value) => filter(value, newAcc)
+ case _ => newAcc
+ }
+ }
+ filter(this, Nil).reverse
+ }
+
+ def extract[A](implicit mf: scala.reflect.Manifest[A]) = Extraction.extract(this)(mf)
+ }
+
+ case object JNothing extends JValue {
+ type Values = Nothing
+ def values = error("nothing contains no values")
+ }
+ case object JNull extends JValue {
+ type Values = Null
+ def values = null
+ }
+ case class JString(s: String) extends JValue {
+ type Values = String
+ def values = s
+ }
+ case class JDouble(num: Double) extends JValue {
+ type Values = Double
+ def values = num
+ }
+ case class JInt(num: BigInt) extends JValue {
+ type Values = BigInt
+ def values = num
+ }
+ case class JBool(value: Boolean) extends JValue {
+ type Values = Boolean
+ def values = value
+ }
+ case class JField(name: String, value: JValue) extends JValue {
+ type Values = (String, value.Values)
+ def values = (name, value.values)
+ override def apply(i: Int): JValue = value(i)
+ }
+ case class JObject(obj: List[JField]) extends JValue {
+ type Values = Map[String, Any]
+ def values = Map() ++ obj.map(_.values.asInstanceOf[(String, Any)]) // FIXME compiler fails if cast is removed
+ }
+ case class JArray(arr: List[JValue]) extends JValue {
+ type Values = List[Any]
+ def values = arr.map(_.values)
+ override def apply(i: Int): JValue = arr(i)
+ }
+
+ def render(value: JValue): Document = value match {
+ case JBool(true) => text("true")
+ case JBool(false) => text("false")
+ case JDouble(n) => text(n.toString)
+ case JInt(n) => text(n.toString)
+ case JNull => text("null")
+ case JNothing => error("can't render 'nothing'")
+ case JString(s) => text("\"" + quote(s) + "\"")
+ case JArray(arr) => text("[") :: series(trimArr(arr).map(render(_))) :: text("]")
+ case JField(n, v) => text("\"" + n + "\":") :: render(v)
+ case JObject(obj) =>
+ val nested = break :: fields(trimObj(obj).map(f => text("\"" + f.name + "\":") :: render(f.value)))
+ text("{") :: nest(2, nested) :: break :: text("}")
+ }
+
+ private def trimArr(xs: List[JValue]) = xs.filter(_ != JNothing)
+ private def trimObj(xs: List[JField]) = xs.filter(_.value != JNothing)
+ private def fold(docs: List[Document]) = docs.foldLeft[Document](empty)(_ :: _)
+ private def series(docs: List[Document]) = fold(punctuate(text(","), docs))
+ private def fields(docs: List[Document]) = fold(punctuate(text(",") :: break, docs))
+ private def punctuate(p: Document, docs: List[Document]): List[Document] = docs match {
+ case Nil => Nil
+ case List(d) => List(d)
+ case d :: ds => (d :: p) :: punctuate(p, ds)
+ }
+
+ private def quote(s: String) = (s.map {
+ case '\r' => "\\r"
+ case '\n' => "\\n"
+ case '\t' => "\\t"
+ case '"' => "\\\""
+ case '\\' => "\\\\"
+ case c if ((c >= '\u0000' && c < '\u001f') || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) => "\\u%04x".format(c.asInstanceOf[Int])
+ case c => c
+ }).mkString
+}
+
+object JsonDSL extends Printer {
+ import JsonAST._
+
+ implicit def int2jvalue(x: Int) = JInt(x)
+ implicit def long2jvalue(x: Long) = JInt(x)
+ implicit def bigint2jvalue(x: BigInt) = JInt(x)
+ implicit def double2jvalue(x: Double) = JDouble(x)
+ implicit def bigdecimal2jvalue(x: BigDecimal) = JDouble(x.doubleValue)
+ implicit def boolean2jvalue(x: Boolean) = JBool(x)
+ implicit def string2jvalue(x: String) = JString(x)
+ implicit def seq2jvalue[A <% JValue](s: Seq[A]) = JArray(s.toList.map { a => val v: JValue = a; v })
+ implicit def option2jvalue[A <% JValue](opt: Option[A]): JValue = opt match {
+ case Some(x) => x
+ case None => JNothing
+ }
+
+ implicit def pair2jvalue[A <% JValue](t: (String, A)) = JObject(List(JField(t._1, t._2)))
+ implicit def list2jvalue(l: List[JField]) = JObject(l)
+ implicit def jobject2assoc(o: JObject) = new JsonListAssoc(o.obj)
+ implicit def pair2Assoc[A <% JValue](t: (String, A)) = new JsonAssoc(t)
+
+ class JsonAssoc[A <% JValue](left: (String, A)) {
+ def ~[B <% JValue](right: (String, B)) = {
+ val l: JValue = left._2
+ val r: JValue = right._2
+ JObject(JField(left._1, l) :: JField(right._1, r) :: Nil)
+ }
+
+ def ~(right: JObject) = {
+ val l: JValue = left._2
+ JObject(JField(left._1, l) :: right.obj)
+ }
+ }
+
+ class JsonListAssoc(left: List[JField]) {
+ def ~(right: (String, JValue)) = JObject(left ::: List(JField(right._1, right._2)))
+ def ~(right: JObject) = JObject(left ::: right.obj)
+ }
+}
+
+trait Printer {
+ import scala.text._
+
+ def compact(d: Document) = {
+ def layout(doc: Document): String = doc match {
+ case DocText(s) => s
+ case DocCons(d1, d2) => layout(d1) + layout(d2)
+ case DocBreak => ""
+ case DocNest(_, d) => layout(d)
+ case DocGroup(d) => layout(d)
+ case DocNil => ""
+ }
+ layout(d)
+ }
+
+ def pretty(d: Document) = {
+ val s = new java.io.StringWriter
+ d.format(80, s)
+ s.toString
+ }
+}
Oops, something went wrong.

0 comments on commit eca4bf9

Please sign in to comment.