Navigation Menu

Skip to content

Commit

Permalink
Merge pull request #1584 from lift/joni_scala_2.10
Browse files Browse the repository at this point in the history
JField is no longer a JValue. It is instead a free standing case class with a name and value. As a result, there now exist mapField, transformField, and findField for working with these new case classes.
  • Loading branch information
farmdawgnation committed Jun 28, 2014
2 parents d88d938 + 17a17cb commit 16a8a22
Show file tree
Hide file tree
Showing 20 changed files with 296 additions and 212 deletions.
Expand Up @@ -111,6 +111,6 @@ trait Base { this: Types =>
}
}
implicit def mapJSONW[A: JSONW]: JSONW[Map[String, A]] = new JSONW[Map[String, A]] {
def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut))
def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut): _*)
}
}
Expand Up @@ -115,6 +115,6 @@ trait Base { this: Types =>
}
}
implicit def mapJSONW[A: JSONW]: JSONW[Map[String, A]] = new JSONW[Map[String, A]] {
def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut))
def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut): _*)
}
}
54 changes: 36 additions & 18 deletions core/json/README.md
Expand Up @@ -10,10 +10,11 @@ a JSON document as a syntax tree.
case class JDouble(num: Double) extends JValue
case class JInt(num: BigInt) extends JValue
case class JBool(value: Boolean) extends JValue
case class JField(name: String, value: JValue) extends JValue
case class JObject(obj: List[JField]) extends JValue
case class JArray(arr: List[JValue]) extends JValue

case class JField(String, JValue)

All features are implemented in terms of above AST. Functions are used to transform
the AST itself, or to transform the AST between different formats. Common transformations
are summarized in a following picture.
Expand Down Expand Up @@ -77,6 +78,25 @@ Applicative style parsing with Scalaz
Migration from older versions
=============================

2.6 ->
-------------------------------------------------

JField is no longer a JValue. This means more type safety since it is no longer possible
to create invalid JSON where JFields are added directly into JArrays for instance. Most
noticeable consequence of this change is that map, transform, find and filter come in
two versions:

def map(f: JValue => JValue): JValue
def mapField(f: JField => JField): JValue
def transform(f: PartialFunction[JValue, JValue]): JValue
def transformField(f: PartialFunction[JField, JField]): JValue
def find(p: JValue => Boolean): Option[JValue]
def findField(p: JField => Boolean): Option[JField]
...

Use *Field functions to traverse fields in the JSON, and use the functions without 'Field'
in the name to traverse values in the JSON.

2.2 ->
------

Expand Down Expand Up @@ -241,7 +261,7 @@ Please see more examples in src/test/scala/net/liftweb/json/MergeExamples.scala
scala> val Diff(changed, added, deleted) = mergedLotto diff lotto1
changed: net.liftweb.json.JsonAST.JValue = JNothing
added: net.liftweb.json.JsonAST.JValue = JNothing
deleted: net.liftweb.json.JsonAST.JValue = JObject(List(JField(lotto,JObject(List(JField(winners,
deleted: net.liftweb.json.JsonAST.JValue = JObject(List((lotto,JObject(List((winners,
JArray(List(JObject(List(JField(winner-id,JInt(54)), JField(numbers,JArray(
List(JInt(52), JInt(3), JInt(12), JInt(11), JInt(18), JInt(22))))))))))))))

Expand Down Expand Up @@ -271,7 +291,7 @@ Please see more examples in src/test/scala/net/liftweb/json/JsonQueryExamples.sc
}
""")

scala> for { JField("age", JInt(age)) <- json } yield age
scala> for { JObject(o) <- json; JField("age", JInt(age)) <- o } yield age
res0: List[BigInt] = List(5, 3)

scala> for {
Expand Down Expand Up @@ -322,15 +342,15 @@ Json AST can be queried using XPath like functions. Following REPL session shows

scala> json \\ "spouse"
res0: net.liftweb.json.JsonAST.JValue = JObject(List(
JField(person,JObject(List(JField(name,JString(Marilyn)), JField(age,JInt(33)))))))
JField(person,JObject(List((name,JString(Marilyn)), (age,JInt(33)))))))

scala> compact(render(res0))
res1: String = {"person":{"name":"Marilyn","age":33}}

scala> compact(render(json \\ "name"))
res2: String = {"name":"Joe","name":"Marilyn"}

scala> compact(render((json remove { _ == JField("name", JString("Marilyn")) }) \\ "name"))
scala> compact(render((json removeField { _ == JField("name", JString("Marilyn")) }) \\ "name"))
res3: String = {"name":"Joe"}

scala> compact(render(json \ "person" \ "name"))
Expand All @@ -339,20 +359,20 @@ Json AST can be queried using XPath like functions. Following REPL session shows
scala> compact(render(json \ "person" \ "spouse" \ "person" \ "name"))
res5: String = "Marilyn"

scala> json find {
scala> json findField {
case JField("name", _) => true
case _ => false
}
res6: Option[net.liftweb.json.JsonAST.JValue] = Some(JField(name,JString(Joe)))
res6: Option[net.liftweb.json.JsonAST.JField] = Some(JField(name,JString(Joe)))

scala> json filter {
scala> json filterField {
case JField("name", _) => true
case _ => false
}
res7: List[net.liftweb.json.JsonAST.JValue] = List(JField(name,JString(Joe)), JField(name,JString(Marilyn)))
res7: List[net.liftweb.json.JsonAST.JField] = List(JField(name,JString(Joe)), JField(name,JString(Marilyn)))

scala> json transform {
case JField("name", JString(s)) => JField("NAME", JString(s.toUpperCase))
scala> json transformField {
case ("name", JString(s)) => ("NAME", JString(s.toUpperCase))
}
res8: net.liftweb.json.JsonAST.JValue = JObject(List(JField(person,JObject(List(
JField(NAME,JString(JOE)), JField(age,JInt(35)), JField(spouse,JObject(List(
Expand Down Expand Up @@ -390,8 +410,6 @@ Indexed path expressions work too and values can be unboxed using type expressio
scala> json \ "children" \\ classOf[JString]
res3: List[net.liftweb.json.JsonAST.JString#Values] = List(Mary, Mazy)

scala> json \ "children" \ classOf[JField]
res4: List[net.liftweb.json.JsonAST.JField#Values] = List((name,Mary), (age,5), (name,Mazy), (age,3))

Extracting values
=================
Expand Down Expand Up @@ -440,8 +458,8 @@ Use back ticks.
Use transform function to postprocess AST.

scala> case class Person(firstname: String)
scala> json transform {
case JField("first-name", x) => JField("firstname", x)
scala> json transformField {
case JField("first-name", x) => ("firstname", x)
}

Extraction function tries to find the best matching constructor when case class has auxiliary
Expand Down Expand Up @@ -645,9 +663,9 @@ decides to use JSON array because there's more than one user-element in XML. The
XML document which happens to have just one user-element will generate a JSON document without JSON array. This
is rarely a desired outcome. These both problems can be fixed by following transformation function.

scala> json transform {
case JField("id", JString(s)) => JField("id", JInt(s.toInt))
case JField("user", x: JObject) => JField("user", JArray(x :: Nil))
scala> json transformField {
case JField("id", JString(s)) => ("id", JInt(s.toInt))
case JField("user", x: JObject) => ("user", JArray(x :: Nil))
}

Other direction is supported too. Converting JSON to XML:
Expand Down
23 changes: 13 additions & 10 deletions core/json/src/main/scala/net/liftweb/json/Diff.scala
Expand Up @@ -30,6 +30,14 @@ case class Diff(changed: JValue, added: JValue, deleted: JValue) {
}
Diff(applyTo(changed), applyTo(added), applyTo(deleted))
}

private[json] def toField(name: String): Diff = {
def applyTo(x: JValue) = x match {
case JNothing => JNothing
case _ => JObject(JField(name, x))
}
Diff(applyTo(changed), applyTo(added), applyTo(deleted))
}
}

/** Computes a diff between two JSONs.
Expand All @@ -39,17 +47,15 @@ object Diff {
* <p>
* Example:<pre>
* val Diff(c, a, d) = ("name", "joe") ~ ("age", 10) diff ("fname", "joe") ~ ("age", 11)
* c = JObject(JField("age",JInt(11)) :: Nil)
* a = JObject(JField("fname",JString("joe")) :: Nil)
* d = JObject(JField("name",JString("joe")) :: Nil)
* c = JObject(("age",JInt(11)) :: Nil)
* a = JObject(("fname",JString("joe")) :: Nil)
* d = JObject(("name",JString("joe")) :: Nil)
* </pre>
*/
def diff(val1: JValue, val2: JValue): Diff = (val1, val2) match {
case (x, y) if x == y => Diff(JNothing, JNothing, JNothing)
case (JObject(xs), JObject(ys)) => diffFields(xs, ys)
case (JArray(xs), JArray(ys)) => diffVals(xs, ys)
case (JField(xn, xv), JField(yn, yv)) if (xn == yn) => diff(xv, yv) map (JField(xn, _))
case (x @ JField(xn, xv), y @ JField(yn, yv)) if (xn != yn) => Diff(JNothing, y, x)
case (JInt(x), JInt(y)) if (x != y) => Diff(JInt(y), JNothing, JNothing)
case (JDouble(x), JDouble(y)) if (x != y) => Diff(JDouble(y), JNothing, JNothing)
case (JString(x), JString(y)) if (x != y) => Diff(JString(y), JNothing, JNothing)
Expand All @@ -62,12 +68,9 @@ object Diff {
case Nil => Diff(JNothing, if (yleft.isEmpty) JNothing else JObject(yleft), JNothing)
case x :: xs => yleft find (_.name == x.name) match {
case Some(y) =>
val Diff(c1, a1, d1) = diff(x, y)
val Diff(c1, a1, d1) = diff(x.value, y.value).toField(y.name)
val Diff(c2, a2, d2) = diffRec(xs, yleft filterNot (_ == y))
Diff(c1 ++ c2, a1 ++ a2, d1 ++ d2) map {
case f: JField => JObject(f :: Nil)
case x => x
}
Diff(c1 merge c2, a1 merge a2, d1 merge d2)
case None =>
val Diff(c, a, d) = diffRec(xs, yleft)
Diff(c, a, JObject(x :: Nil) merge d)
Expand Down
17 changes: 5 additions & 12 deletions core/json/src/main/scala/net/liftweb/json/Extraction.scala
Expand Up @@ -63,7 +63,7 @@ object Extraction {
*/
def decompose(a: Any)(implicit formats: Formats): JValue = {
def prependTypeHint(clazz: Class[_], o: JObject) =
JField(formats.typeHintFieldName, JString(formats.typeHints.hintFor(clazz))) ++ o
JObject(JField(formats.typeHintFieldName, JString(formats.typeHints.hintFor(clazz))) :: o.obj)

def mkObject(clazz: Class[_], fields: List[JField]) = formats.typeHints.containsHint_?(clazz) match {
case true => prependTypeHint(clazz, JObject(fields))
Expand Down Expand Up @@ -120,8 +120,9 @@ object Extraction {
case JDouble(num) => Map(path -> num.toString)
case JInt(num) => Map(path -> num.toString)
case JBool(value) => Map(path -> value.toString)
case JField(name, value) => flatten0(path + escapePath(name), value)
case JObject(obj) => obj.foldLeft(Map[String, String]()) { (map, field) => map ++ flatten0(path + ".", field) }
case JObject(obj) => obj.foldLeft(Map[String, String]()) { case (map, JField(name, value)) =>
map ++ flatten0(path + "." + escapePath(name), value)
}
case JArray(arr) => arr.length match {
case 0 => Map(path -> "[]")
case _ => arr.foldLeft((Map[String, String](), 0)) {
Expand Down Expand Up @@ -209,7 +210,6 @@ object Extraction {
else {
val argNames = json match {
case JObject(fs) => fs.map(_.name)
case JField(name, _) => List(name)
case x => Nil
}
constructor.bestMatching(argNames)
Expand Down Expand Up @@ -282,7 +282,6 @@ object Extraction {
else json match {
case JNull => null
case JObject(TypeHint(t, fs)) => mkWithTypeHint(t, fs, constructor.targetType)
case JField(_, JObject(TypeHint(t, fs))) => mkWithTypeHint(t, fs, constructor.targetType)
case _ => instantiate
}
}
Expand Down Expand Up @@ -314,7 +313,7 @@ object Extraction {
case Value(targetType) => convert(root, targetType, formats)
case c: Constructor => newInstance(c, root)
case Cycle(targetType) => build(root, mappingOf(targetType))
case Arg(path, m, optional) => mkValue(fieldValue(root), m, path, optional)
case Arg(path, m, optional) => mkValue(root, m, path, optional)
case Col(targetType, m) =>
val custom = formats.customDeserializer(formats)
val c = targetType.clazz
Expand Down Expand Up @@ -358,11 +357,6 @@ object Extraction {
}
}

def fieldValue(json: JValue): JValue = json match {
case JField(_, value) => value
case x => x
}

build(json, mapping)
}

Expand Down Expand Up @@ -401,7 +395,6 @@ object Extraction {
case j: JArray if (targetType == classOf[JArray]) => j
case JNull => null
case JNothing => fail("Did not find value which can be converted into " + targetType.getName)
case JField(_, x) => convert(x, targetType, formats)
case _ =>
val custom = formats.customDeserializer(formats)
val typeInfo = TypeInfo(targetType, None)
Expand Down

0 comments on commit 16a8a22

Please sign in to comment.