From fa917897bb81a87682abdc3c15e961b4bc78889c Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Thu, 24 Nov 2011 09:09:41 +0200 Subject: [PATCH 1/5] 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. --- core/json/README.md | 64 +++--- .../main/scala/net/liftweb/json/Diff.scala | 25 +-- .../scala/net/liftweb/json/Extraction.scala | 29 ++- .../main/scala/net/liftweb/json/JsonAST.scala | 184 +++++++++++++----- .../scala/net/liftweb/json/JsonParser.scala | 50 ++--- .../main/scala/net/liftweb/json/Merge.scala | 10 +- .../src/main/scala/net/liftweb/json/Xml.scala | 12 +- .../scala/net/liftweb/json/DiffExamples.scala | 4 - .../scala/net/liftweb/json/Examples.scala | 17 +- .../liftweb/json/ExtractionExamplesSpec.scala | 1 - .../scala/net/liftweb/json/JValueGen.scala | 2 +- .../scala/net/liftweb/json/JsonAstSpec.scala | 10 +- .../scala/net/liftweb/json/LottoExample.scala | 4 - .../scala/net/liftweb/json/XmlExamples.scala | 20 +- 14 files changed, 257 insertions(+), 175 deletions(-) diff --git a/core/json/README.md b/core/json/README.md index 327f33f0db..725f298fee 100644 --- a/core/json/README.md +++ b/core/json/README.md @@ -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 + type 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. @@ -77,6 +78,25 @@ Applicative style parsing with Scalaz Migration from older versions ============================= +2.x (FIXME: add correct version once released) -> +------------------------------------------------- + +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 -> ------ @@ -98,7 +118,7 @@ Any valid json can be parsed into internal AST format. scala> import net.liftweb.json._ scala> parse(""" { "numbers" : [1, 2, 3, 4] } """) res0: net.liftweb.json.JsonAST.JValue = - JObject(List(JField(numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) + JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) Producing JSON ============== @@ -241,8 +261,8 @@ 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, - JArray(List(JObject(List(JField(winner-id,JInt(54)), JField(numbers,JArray( + deleted: net.liftweb.json.JsonAST.JValue = JObject(List((lotto,JObject(List((winners, + JArray(List(JObject(List((winner-id,JInt(54)), (numbers,JArray( List(JInt(52), JInt(3), JInt(12), JInt(11), JInt(18), JInt(22)))))))))))))) @@ -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 { @@ -322,7 +342,7 @@ 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))))))) + (person,JObject(List((name,JString(Marilyn)), (age,JInt(33))))))) scala> compact(render(res0)) res1: String = {"person":{"name":"Marilyn","age":33}} @@ -330,7 +350,7 @@ Json AST can be queried using XPath like functions. Following REPL session shows 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")) @@ -339,24 +359,24 @@ 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.JValue] = Some((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))) - 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( - JField(person,JObject(List(JField(NAME,JString(MARILYN)), JField(age,JInt(33))))))))))))) + res8: net.liftweb.json.JsonAST.JValue = JObject(List((person,JObject(List( + (NAME,JString(JOE)), (age,JInt(35)), (spouse,JObject(List( + (person,JObject(List((NAME,JString(MARILYN)), (age,JInt(33))))))))))))) scala> json.values res8: scala.collection.immutable.Map[String,Any] = Map(person -> Map(name -> Joe, age -> 35, spouse -> Map(person -> Map(name -> Marilyn, age -> 33)))) @@ -379,7 +399,7 @@ Indexed path expressions work too and values can be unboxed using type expressio """) scala> (json \ "children")(0) - res0: net.liftweb.json.JsonAST.JValue = JObject(List(JField(name,JString(Mary)), JField(age,JInt(5)))) + res0: net.liftweb.json.JsonAST.JValue = JObject(List((name,JString(Mary)), (age,JInt(5)))) scala> (json \ "children")(1) \ "name" res1: net.liftweb.json.JsonAST.JValue = JString(Mazy) @@ -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 ================= @@ -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 ("first-name", x) => ("firstname", x) } Extraction function tries to find the best matching constructor when case class has auxiliary @@ -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 ("id", JString(s)) => ("id", JInt(s.toInt)) + case ("user", x: JObject) => ("user", JArray(x :: Nil)) } Other direction is supported too. Converting JSON to XML: diff --git a/core/json/src/main/scala/net/liftweb/json/Diff.scala b/core/json/src/main/scala/net/liftweb/json/Diff.scala index 805119fd9f..0284826338 100644 --- a/core/json/src/main/scala/net/liftweb/json/Diff.scala +++ b/core/json/src/main/scala/net/liftweb/json/Diff.scala @@ -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((name, x)) + } + Diff(applyTo(changed), applyTo(added), applyTo(deleted)) + } } /** Computes a diff between two JSONs. @@ -39,17 +47,15 @@ object Diff { *

* Example:

    * 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)
    * 
*/ 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) @@ -60,14 +66,11 @@ object Diff { private def diffFields(vs1: List[JField], vs2: List[JField]) = { def diffRec(xleft: List[JField], yleft: List[JField]): Diff = xleft match { case Nil => Diff(JNothing, if (yleft.isEmpty) JNothing else JObject(yleft), JNothing) - case x :: xs => yleft find (_.name == x.name) match { + case x :: xs => yleft find (_._1 == x._1) match { case Some(y) => - val Diff(c1, a1, d1) = diff(x, y) + val Diff(c1, a1, d1) = diff(x._2, y._2).toField(y._1) 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) diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 2cf10eaa47..fe1d0c711f 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -62,7 +62,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)) @@ -100,7 +100,7 @@ object Extraction { .getOrElse(JField(n, JNothing)) } } getOrElse Nil - val uniqueFields = fields filterNot (f => args.find(_.name == f.name).isDefined) + val uniqueFields = fields filterNot (f => args.find(_._1 == f._1).isDefined) mkObject(x.getClass, uniqueFields ++ args) } } @@ -119,8 +119,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, (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)) { @@ -207,8 +208,7 @@ object Extraction { if (constructor.choices.size == 1) constructor.choices.head // optimized common case else { val argNames = json match { - case JObject(fs) => fs.map(_.name) - case JField(name, _) => List(name) + case JObject(fs) => fs.map(_._1) case x => Nil } constructor.bestMatching(argNames) @@ -266,7 +266,7 @@ object Extraction { } def mkWithTypeHint(typeHint: String, fields: List[JField], typeInfo: TypeInfo) = { - val obj = JObject(fields filterNot (_.name == formats.typeHintFieldName)) + val obj = JObject(fields filterNot (_._1 == formats.typeHintFieldName)) val deserializer = formats.typeHints.deserialize if (!deserializer.isDefinedAt(typeHint, obj)) { val concreteClass = formats.typeHints.classFor(typeHint) getOrElse fail("Do not know how to deserialize '" + typeHint + "'") @@ -281,7 +281,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 } } @@ -290,9 +289,9 @@ object Extraction { def unapply(fs: List[JField]): Option[(String, List[JField])] = if (formats.typeHints == NoTypeHints) None else { - val grouped = fs groupBy (_.name == formats.typeHintFieldName) + val grouped = fs groupBy (_._1 == formats.typeHintFieldName) if (grouped.isDefinedAt(true)) - Some((grouped(true).head.value.values.toString, grouped.get(false).getOrElse(Nil))) + Some((grouped(true).head._2.values.toString, grouped.get(false).getOrElse(Nil))) else None } } @@ -313,7 +312,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 @@ -324,7 +323,7 @@ object Extraction { else if (classOf[Seq[_]].isAssignableFrom(c)) newCollection(root, m, a => List(a: _*)) else fail("Expected collection but got " + m + " for class " + c) case Dict(m) => root match { - case JObject(xs) => Map(xs.map(x => (x.name, build(x.value, m))): _*) + case JObject(xs) => Map(xs.map(x => (x._1, build(x._2, m))): _*) case x => fail("Expected object but got " + x) } } @@ -357,11 +356,6 @@ object Extraction { } } - def fieldValue(json: JValue): JValue = json match { - case JField(_, value) => value - case x => x - } - build(json, mapping) } @@ -399,7 +393,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) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 9faad3b4b1..d44ee27b1d 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -32,7 +32,7 @@ object JsonAST { object JValue extends Merge.Mergeable /** - * Data type for Json AST. + * Data type for JSON AST. */ sealed abstract class JValue extends Diff.Diffable { type Values @@ -44,24 +44,27 @@ object JsonAST { * json \ "name" * */ - def \(nameToFind: String): JValue = { - val p = (json: JValue) => json match { - case JField(name, value) if name == nameToFind => true - case _ => false - } - findDirect(children, p) match { + def \(nameToFind: String): JValue = + findDirectByName(List(this), nameToFind) match { case Nil => JNothing - case JField(_, x) :: Nil => x case x :: Nil => x case x => JArray(x) } + + private def findDirectByName(xs: List[JValue], name: String): List[JValue] = xs.flatMap { + case JObject(l) => l.filter { + case (n, _) if n == name => true + case _ => false + } map (_._2) + case JArray(l) => findDirectByName(l, name) + case _ => Nil } private def findDirect(xs: List[JValue], p: JValue => Boolean): List[JValue] = xs.flatMap { case JObject(l) => l.filter { - case x if p(x) => true + case (n, x) if p(x) => true case _ => false - } + } map (_._2) case JArray(l) => findDirect(l, p) case x if p(x) => x :: Nil case _ => Nil @@ -75,16 +78,16 @@ object JsonAST { */ def \\(nameToFind: String): JValue = { 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 JObject(l) => l.foldLeft(List[JField]()) { + case (a, (name, value)) => + if (name == nameToFind) a ::: List((name, value)) ::: find(value) else a ::: find(value) + } + case JArray(l) => l.foldLeft(List[JField]())((a, json) => a ::: find(json)) case _ => Nil } find(this) match { - case JField(_, x) :: Nil => x - case x :: Nil => x - case x => JObject(x) + case (_, x) :: Nil => x + case xs => JObject(xs) } } @@ -135,10 +138,9 @@ object JsonAST { * JArray(JInt(1) :: JInt(2) :: Nil).children == List(JInt(1), JInt(2)) * */ - def children = this match { - case JObject(l) => l + def children: List[JValue] = this match { + case JObject(l) => l map (_._2) case JArray(l) => l - case JField(n, v) => List(v) case _ => Nil } @@ -149,37 +151,83 @@ object JsonAST { def rec(acc: A, v: JValue) = { val newAcc = f(acc, v) v match { - case JObject(l) => l.foldLeft(newAcc)((a, e) => e.fold(a)(f)) + case JObject(l) => l.foldLeft(newAcc) { case (a, (name, value)) => value.fold(a)(f) } case JArray(l) => l.foldLeft(newAcc)((a, e) => e.fold(a)(f)) - case JField(_, value) => value.fold(newAcc)(f) case _ => newAcc } } rec(z, this) } + /** Return a combined value by folding over JSON by applying a function f + * for each field. The initial value is z. + */ + def foldField[A](z: A)(f: (A, JField) => A): A = { + def rec(acc: A, v: JValue) = { + v match { + case JObject(l) => l.foldLeft(acc) { + case (a, field@(name, value)) => value.foldField(f(a, field))(f) + } + case JArray(l) => l.foldLeft(acc)((a, e) => e.foldField(a)(f)) + case _ => acc + } + } + rec(z, this) + } + /** Return a new JValue resulting from applying the given function f - * to each element in JSON. + * to each value in JSON. *

* Example:

-     * JArray(JInt(1) :: JInt(2) :: Nil) map { case JInt(x) => JInt(x+1); case x => x }
+     * JArray(JInt(1) :: JInt(2) :: Nil) map {
+     *   case JInt(x) => JInt(x+1)
+     *   case x => x
+     * }
      * 
*/ def map(f: JValue => JValue): JValue = { def rec(v: JValue): JValue = v match { - case JObject(l) => f(JObject(l.map(f => rec(f) match { - case x: JField => x - case x => JField(f.name, x) - }))) + case JObject(l) => f(JObject(l.map { case (n, v) => (n, rec(v)) })) case JArray(l) => f(JArray(l.map(rec))) - case JField(name, value) => f(JField(name, rec(value))) case x => f(x) } rec(this) } + /** Return a new JValue resulting from applying the given function f + * to each field in JSON. + *

+ * Example:

+     * JObject(("age", JInt(10)) :: Nil) map {
+     *   case ("age", JInt(x)) => ("age", JInt(x+1))
+     *   case x => x
+     * }
+     * 
+ */ + def mapField(f: JField => JField): JValue = { + def rec(v: JValue): JValue = v match { + case JObject(l) => JObject(l.map { case (n, v) => f(n, rec(v)) }) + case JArray(l) => JArray(l.map(rec)) + case x => x + } + rec(this) + } + + /** Return a new JValue resulting from applying the given partial function f + * to each field in JSON. + *

+ * Example:

+     * JObject(("age", JInt(10)) :: Nil) transformField {
+     *   case ("age", JInt(x)) => ("age", JInt(x+1))
+     * }
+     * 
+ */ + def transformField(f: PartialFunction[JField, JField]): JValue = mapField { x => + if (f.isDefinedAt(x)) f(x) else x + } + /** Return a new JValue resulting from applying the given partial function f - * to each element in JSON. + * to each value in JSON. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil) transform { case JInt(x) => JInt(x+1) }
@@ -218,6 +266,22 @@ object JsonAST {
       rep(l, this)
     }
 
+    /** Return the first field from JSON which matches the given predicate.
+     * 

+ * Example:

+     * JObject(("age", JInt(2))) findField { case (n, v) => n == "age" }
+     * 
+ */ + def findField(p: JField => Boolean): Option[JField] = { + def find(json: JValue): Option[JField] = json match { + case JObject(fs) if (fs find p).isDefined => return fs find p + case JObject(fs) => fs.flatMap { case (n, v) => find(v) }.headOption + case JArray(l) => l.flatMap(find _).headOption + case _ => None + } + find(this) + } + /** Return the first element from JSON which matches the given predicate. *

* Example:

@@ -228,16 +292,27 @@ object JsonAST {
       def find(json: JValue): Option[JValue] = {
         if (p(json)) return Some(json)
         json match {
-          case JObject(l) => l.flatMap(find _).headOption
+          case JObject(fs) => fs.flatMap { case (n, v) => find(v) }.headOption
           case JArray(l) => l.flatMap(find _).headOption
-          case JField(_, value) => find(value)
           case _ => None
         }
       }
       find(this)
     }
 
-    /** Return a List of all elements which matches the given predicate.
+    /** Return a List of all fields which matches the given predicate.
+     * 

+ * Example:

+     * JObject(("age", JInt(10)) :: Nil) filterField {
+     *   case ("age", JInt(x)) if x > 18 => true
+     *   case _          => false
+     * }
+     * 
+ */ + def filterField(p: JField => Boolean): List[JField] = + foldField(List[JField]())((acc, e) => if (p(e)) e :: acc else acc).reverse + + /** Return a List of all values which matches the given predicate. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil) filter { case JInt(x) => x > 1; case _ => false }
@@ -258,19 +333,29 @@ object JsonAST {
       def append(value1: JValue, value2: JValue): JValue = (value1, value2) match {
         case (JNothing, x) => x
         case (x, JNothing) => x
-        case (JObject(xs), x: JField) => JObject(xs ::: List(x))
-        case (x: JField, JObject(xs)) => JObject(x :: xs)
         case (JArray(xs), JArray(ys)) => JArray(xs ::: ys)
         case (JArray(xs), v: JValue) => JArray(xs ::: List(v))
         case (v: JValue, JArray(xs)) => JArray(v :: xs)
-        case (f1: JField, f2: JField) => JObject(f1 :: f2 :: Nil)
-        case (JField(n, v1), v2: JValue) => JField(n, append(v1, v2))
         case (x, y) => JArray(x :: y :: Nil)
       }
       append(this, other)
     }
 
-    /** Return a JSON where all elements matching the given predicate are removed.
+    /** Return a JSON where all fields matching the given predicate are removed.
+     * 

+ * Example:

+     * JObject(("age", JInt(10)) :: Nil) removeField {
+     *   case ("age", _) => true
+     *   case _          => false
+     * }
+     * 
+ */ + def removeField(p: JField => Boolean): JValue = this mapField { + case x if p(x) => (x._1, JNothing) + case x => x + } + + /** Return a JSON where all values matching the given predicate are removed. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: JNull :: Nil) remove { _ == JNull }
@@ -365,26 +450,32 @@ object JsonAST {
     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 : (String, Any))
+    def values = obj.map { case (n, v) => (n, v.values) } toMap
 
     override def equals(that: Any): Boolean = that match {
       case o: JObject => Set(obj.toArray: _*) == Set(o.obj.toArray: _*)
       case _ => false
     }
   }
+  case object JObject {
+    def apply(fs: JField*): JObject = JObject(fs.toList)
+  }
+
   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)
   }
 
+  type JField = (String, JValue)
+  object JField {
+    def apply(name: String, value: JValue) = (name, value)
+    def unapply(f: JField): Option[(String, JValue)] = Some(f)
+  }
+
   /** Renders JSON.
    * @see Printer#compact
    * @see Printer#pretty
@@ -400,14 +491,13 @@ object JsonAST {
     case JString(null) => text("null")
     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)))
+      val nested = break :: fields(trimObj(obj).map { case (name, value) => text("\"" + name + "\":") :: render(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 trimObj(xs: List[JField]) = xs.filter(_._2 != JNothing)
   private def series(docs: List[Document]) = fold(punctuate(text(","), docs))
   private def fields(docs: List[Document]) = fold(punctuate(text(",") :: break, docs))
   private def fold(docs: List[Document]) = docs.foldLeft[Document](empty)(_ :: _)
diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
index ca495795f8..6fda6e0366 100644
--- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
+++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala
@@ -145,36 +145,36 @@ object JsonParser {
 
     // This is a slightly faster way to correct order of fields and arrays than using 'map'.
     def reverse(v: JValue): JValue = v match {
-      case JObject(l) => JObject(l.map(reverse).asInstanceOf[List[JField]].reverse)
+      case JObject(l) => JObject((l.map { case (n, v) => (n, reverse(v)) }).reverse)
       case JArray(l) => JArray(l.map(reverse).reverse)
-      case JField(name, value) => JField(name, reverse(value))
       case x => x
     }
 
-    def closeBlock(v: JValue) {
+    def closeBlock(v: Any) {
+      @inline def toJValue(x: Any) = x match {
+        case json: JValue => json
+        case _ => p.fail("unexpected field " + x)
+      }
+
       vals.peekOption match {
-        case Some(f: JField) =>
-          val field = vals.pop(classOf[JField])
-          val newField = JField(field.name, v)
+        case Some((name: String, value)) =>
+          vals.pop(classOf[JField])
           val obj = vals.peek(classOf[JObject])
-          vals.replace(JObject(newField :: obj.obj))
-        case Some(o: JObject) => v match {
-          case x: JField => vals.replace(JObject(x :: o.obj))
-          case _ => p.fail("expected field but got " + v)
-        }
-        case Some(a: JArray) => vals.replace(JArray(v :: a.arr))
+          vals.replace(JObject((name, toJValue(v)) :: obj.obj))
+        case Some(o: JObject) => 
+          vals.replace(JObject(vals.peek(classOf[JField]) :: o.obj))
+        case Some(a: JArray) => vals.replace(JArray(toJValue(v) :: a.arr))
         case Some(x) => p.fail("expected field, array or object but got " + x)
-        case None => root = Some(reverse(v))
+        case None => root = Some(reverse(toJValue(v)))
       }
     }
 
     def newValue(v: JValue) {
-      vals.peek(classOf[JValue]) match {
-        case f: JField =>
+      vals.peekAny match {
+        case (name: String, value) =>
           vals.pop(classOf[JField])
-          val newField = JField(f.name, v)
           val obj = vals.peek(classOf[JObject])
-          vals.replace(JObject(newField :: obj.obj))
+          vals.replace(JObject((name, v) :: obj.obj))
         case a: JArray => vals.replace(JArray(v :: a.arr))
         case _ => p.fail("expected field or array")
       }
@@ -190,7 +190,7 @@ object JsonParser {
         case DoubleVal(x)     => newValue(JDouble(x))
         case BoolVal(x)       => newValue(JBool(x))
         case NullVal          => newValue(JNull)
-        case CloseObj         => closeBlock(vals.pop(classOf[JValue]))
+        case CloseObj         => closeBlock(vals.popAny)
         case OpenArr          => vals.push(JArray(Nil))
         case CloseArr         => closeBlock(vals.pop(classOf[JArray]))
         case End              =>
@@ -204,14 +204,16 @@ object JsonParser {
 
   private class ValStack(parser: Parser) {
     import java.util.LinkedList
-    private[this] val stack = new LinkedList[JValue]()
+    private[this] val stack = new LinkedList[Any]()
 
-    def pop[A <: JValue](expectedType: Class[A]) = convert(stack.poll, expectedType)
-    def push(v: JValue) = stack.addFirst(v)
-    def peek[A <: JValue](expectedType: Class[A]) = convert(stack.peek, expectedType)
-    def replace[A <: JValue](newTop: JValue) = stack.set(0, newTop)
+    def popAny = stack.poll
+    def pop[A](expectedType: Class[A]) = convert(stack.poll, expectedType)
+    def push(v: Any) = stack.addFirst(v)
+    def peekAny = stack.peek
+    def peek[A](expectedType: Class[A]) = convert(stack.peek, expectedType)
+    def replace[A](newTop: Any) = stack.set(0, newTop)
 
-    private def convert[A <: JValue](x: JValue, expectedType: Class[A]): A = {
+    private def convert[A](x: Any, expectedType: Class[A]): A = {
       if (x == null) parser.fail("expected object or array")
       try { x.asInstanceOf[A] } catch { case _: ClassCastException => parser.fail("unexpected " + x) }
     }
diff --git a/core/json/src/main/scala/net/liftweb/json/Merge.scala b/core/json/src/main/scala/net/liftweb/json/Merge.scala
index dcbb0ee5bd..e95cf6fce9 100644
--- a/core/json/src/main/scala/net/liftweb/json/Merge.scala
+++ b/core/json/src/main/scala/net/liftweb/json/Merge.scala
@@ -35,8 +35,6 @@ private [json] trait LowPriorityMergeDep {
     private def merge(val1: JValue, val2: JValue): JValue = (val1, val2) match {
       case (JObject(xs), JObject(ys)) => JObject(Merge.mergeFields(xs, ys))
       case (JArray(xs), JArray(ys)) => JArray(Merge.mergeVals(xs, ys))
-      case (JField(n1, v1), JField(n2, v2)) if n1 == n2 => JField(n1, merge(v1, v2))
-      case (f1: JField, f2: JField) => f2
       case (JNothing, x) => x
       case (x, JNothing) => x
       case (_, y) => y
@@ -61,7 +59,7 @@ object Merge {
    * 

* Example:

    * val m = ("name", "joe") ~ ("age", 10) merge ("name", "joe") ~ ("iq", 105)
-   * m: JObject(List(JField(name,JString(joe)), JField(age,JInt(10)), JField(iq,JInt(105))))
+   * m: JObject(List((name,JString(joe)), (age,JInt(10)), (iq,JInt(105))))
    * 
*/ def merge[A <: JValue, B <: JValue, R <: JValue] @@ -70,9 +68,9 @@ object Merge { private[json] def mergeFields(vs1: List[JField], vs2: List[JField]): List[JField] = { def mergeRec(xleft: List[JField], yleft: List[JField]): List[JField] = xleft match { case Nil => yleft - case JField(xn, xv) :: xs => yleft find (_.name == xn) match { - case Some(y @ JField(yn, yv)) => - JField(xn, merge(xv, yv)) :: mergeRec(xs, yleft filterNot (_ == y)) + case (xn, xv) :: xs => yleft find (_._1 == xn) match { + case Some(y @ (yn, yv)) => + (xn, merge(xv, yv)) :: mergeRec(xs, yleft filterNot (_ == y)) case None => JField(xn, xv) :: mergeRec(xs, yleft) } } diff --git a/core/json/src/main/scala/net/liftweb/json/Xml.scala b/core/json/src/main/scala/net/liftweb/json/Xml.scala index 720076c855..8f22693bc7 100644 --- a/core/json/src/main/scala/net/liftweb/json/Xml.scala +++ b/core/json/src/main/scala/net/liftweb/json/Xml.scala @@ -107,11 +107,11 @@ object Xml { case XLeaf((name, value), attrs) => (value, attrs) match { case (_, Nil) => toJValue(value) case (XValue(""), xs) => JObject(mkFields(xs)) - case (_, xs) => JObject(JField(name, toJValue(value)) :: mkFields(xs)) + case (_, xs) => JObject((name, toJValue(value)) :: mkFields(xs)) } case XNode(xs) => JObject(mkFields(xs)) case XArray(elems) => JArray(elems.map(toJValue)) - } + } def mkFields(xs: List[(String, XElem)]) = xs.flatMap { case (name, value) => (value, toJValue(value)) match { @@ -145,7 +145,7 @@ object Xml { case List(x @ XLeaf(_, _ :: _)) => toJValue(x) case List(x) => JObject(JField(nameOf(xml.head), toJValue(x)) :: Nil) case x => JArray(x.map(toJValue)) - } + } } /** Convert given JSON to XML. @@ -169,9 +169,8 @@ object Xml { */ def toXml(json: JValue): NodeSeq = { def toXml(name: String, json: JValue): NodeSeq = json match { - case JObject(fields) => new XmlNode(name, fields flatMap { f => toXml(f.name, f.value) }) + case JObject(fields) => new XmlNode(name, fields flatMap { case (n, v) => toXml(n, v) }) case JArray(xs) => xs flatMap { v => toXml(name, v) } - case JField(n, v) => new XmlNode(name, toXml(n, v)) case JInt(x) => new XmlElem(name, x.toString) case JDouble(x) => new XmlElem(name, x.toString) case JString(x) => new XmlElem(name, x) @@ -181,8 +180,7 @@ object Xml { } json match { - case JField(n, v) => toXml(n, v) - case JObject(fields) => fields flatMap { f => toXml(f.name, f.value) } + case JObject(fields) => fields flatMap { case (name, value) => toXml(name, value) } case x => toXml("root", x) } } diff --git a/core/json/src/test/scala/net/liftweb/json/DiffExamples.scala b/core/json/src/test/scala/net/liftweb/json/DiffExamples.scala index 85eb4d959d..c103b213e6 100644 --- a/core/json/src/test/scala/net/liftweb/json/DiffExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/DiffExamples.scala @@ -19,10 +19,6 @@ package json import org.specs.Specification - -/** - * System under specification for Diff Examples. - */ object DiffExamples extends Specification("Diff Examples") { import MergeExamples.{scala1, scala2, lotto1, lotto2, mergedLottoResult} diff --git a/core/json/src/test/scala/net/liftweb/json/Examples.scala b/core/json/src/test/scala/net/liftweb/json/Examples.scala index 97b6a38dcf..61d3a358a9 100644 --- a/core/json/src/test/scala/net/liftweb/json/Examples.scala +++ b/core/json/src/test/scala/net/liftweb/json/Examples.scala @@ -19,7 +19,6 @@ package json import org.specs.Specification - object Examples extends Specification { import JsonAST.concat import JsonDSL._ @@ -40,26 +39,26 @@ object Examples extends Specification { } "Transformation example" in { - val uppercased = parse(person).transform { case JField(n, v) => JField(n.toUpperCase, v) } + val uppercased = parse(person).transformField { case JField(n, v) => JField(n.toUpperCase, v) } val rendered = compact(render(uppercased)) rendered mustEqual """{"PERSON":{"NAME":"Joe","AGE":35,"SPOUSE":{"PERSON":{"NAME":"Marilyn","AGE":33}}}}""" } "Remove example" in { - val json = parse(person) remove { _ == JField("name", "Marilyn") } + val json = parse(person) removeField { _ == JField("name", "Marilyn") } compact(render(json \\ "name")) mustEqual """{"name":"Joe"}""" } "Queries on person example" in { val json = parse(person) - val filtered = json filter { + val filtered = json filterField { case JField("name", _) => true case _ => false } filtered mustEqual List(JField("name", JString("Joe")), JField("name", JString("Marilyn"))) - val found = json find { + val found = json findField { case JField("name", _) => true case _ => false } @@ -68,10 +67,10 @@ object Examples extends Specification { "Object array example" in { val json = parse(objArray) - compact(render(json \ "children" \ "name")) mustEqual """["name":"Mary","name":"Mazy"]""" + compact(render(json \ "children" \ "name")) mustEqual """["Mary","Mazy"]""" compact(render((json \ "children")(0) \ "name")) mustEqual "\"Mary\"" compact(render((json \ "children")(1) \ "name")) mustEqual "\"Mazy\"" - (for { JField("name", JString(y)) <- json } yield y) mustEqual List("joe", "Mary", "Mazy") + (for { JObject(o) <- json; JField("name", JString(y)) <- o } yield y) mustEqual List("joe", "Mary", "Mazy") } "Unbox values using XPath-like type expression" in { @@ -109,13 +108,13 @@ object Examples extends Specification { } "JSON building example" in { - val json = concat(JField("name", JString("joe")), JField("age", JInt(34))) ++ concat(JField("name", JString("mazy")), JField("age", JInt(31))) + val json = JObject(("name", JString("joe")), ("age", JInt(34))) ++ JObject(("name", ("mazy")), ("age", JInt(31))) compact(render(json)) mustEqual """[{"name":"joe","age":34},{"name":"mazy","age":31}]""" } "JSON building with implicit primitive conversions example" in { import Implicits._ - val json = concat(JField("name", "joe"), JField("age", 34)) ++ concat(JField("name", "mazy"), JField("age", 31)) + val json = JObject(("name", "joe"), ("age", 34)) ++ JObject(("name", "mazy"), ("age", 31)) compact(render(json)) mustEqual """[{"name":"joe","age":34},{"name":"mazy","age":31}]""" } diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala index a42eacc472..fe125cdd9f 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala @@ -72,7 +72,6 @@ object ExtractionExamples extends Specification("Extraction Examples Specificati JInt(1).extract[Int] mustEqual 1 JInt(1).extract[String] mustEqual "1" - JField("foo", JInt(1)).extract[Int] mustEqual 1 } "Primitive extraction example" in { diff --git a/core/json/src/test/scala/net/liftweb/json/JValueGen.scala b/core/json/src/test/scala/net/liftweb/json/JValueGen.scala index 8f037c7f44..fa1745a56c 100644 --- a/core/json/src/test/scala/net/liftweb/json/JValueGen.scala +++ b/core/json/src/test/scala/net/liftweb/json/JValueGen.scala @@ -39,7 +39,7 @@ trait JValueGen { def genJValueClass: Gen[Class[_ <: JValue]] = oneOf( JNull.getClass.asInstanceOf[Class[JValue]], JNothing.getClass.asInstanceOf[Class[JValue]], classOf[JInt], - classOf[JDouble], classOf[JBool], classOf[JString], classOf[JField], classOf[JArray], classOf[JObject]) + classOf[JDouble], classOf[JBool], classOf[JString], classOf[JArray], classOf[JObject]) def listSize = choose(0, 5).sample.get } diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala index 808ca3dfd4..1ad42c1bdf 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala @@ -21,10 +21,6 @@ import org.specs.{ScalaCheck, Specification} import org.scalacheck._ import org.scalacheck.Prop.{forAll, forAllNoShrink} - -/** - * System under specification for JSON AST. - */ object JsonAstSpec extends Specification("JSON AST Specification") with JValueGen with ScalaCheck { "Functor identity" in { val identityProp = (json: JValue) => json == (json map identity) @@ -94,12 +90,11 @@ object JsonAstSpec extends Specification("JSON AST Specification") with JValueGe forAll(removeNothingProp) must pass } - "Remove removes only matching elements (in case of a field, its value is set to JNothing)" in { + "Remove removes only matching elements" in { forAllNoShrink(genJValue, genJValueClass) { (json: JValue, x: Class[_ <: JValue]) => { val removed = json remove typePredicate(x) val Diff(c, a, d) = json diff removed val elemsLeft = removed filter { - case JField(_, JNothing) => false case _ => true } c == JNothing && a == JNothing && elemsLeft.forall(_.getClass != x) @@ -109,9 +104,8 @@ object JsonAstSpec extends Specification("JSON AST Specification") with JValueGe "Replace one" in { val anyReplacement = (x: JValue, replacement: JObject) => { def findOnePath(jv: JValue, l: List[String]): List[String] = jv match { - case JField(name, value) => findOnePath(value, name :: l) case JObject(fl) => fl match { - case field :: xs => findOnePath(field, l) + case field :: xs => findOnePath(field._2, l) case Nil => l } case _ => l diff --git a/core/json/src/test/scala/net/liftweb/json/LottoExample.scala b/core/json/src/test/scala/net/liftweb/json/LottoExample.scala index 040fe069e2..e6f51a4772 100644 --- a/core/json/src/test/scala/net/liftweb/json/LottoExample.scala +++ b/core/json/src/test/scala/net/liftweb/json/LottoExample.scala @@ -19,10 +19,6 @@ package json import org.specs.Specification - -/** - * System under specification for Lotto Examples. - */ object LottoExample extends Specification("Lotto Examples") { import JsonDSL._ diff --git a/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala b/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala index ec01269d75..77bf3a7806 100644 --- a/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala @@ -19,10 +19,6 @@ package json import org.specs.Specification - -/** - * System under specification for Xml Examples. - */ object XmlExamples extends Specification("XML Examples") { import JsonDSL._ import Xml._ @@ -34,14 +30,14 @@ object XmlExamples extends Specification("XML Examples") { } "Conversion transformation example 1" in { - val json = toJson(users1).transform { + val json = toJson(users1).transformField { case JField("id", JString(s)) => JField("id", JInt(s.toInt)) } compact(render(json)) mustEqual """{"users":{"count":"2","user":[{"disabled":"true","id":1,"name":"Harry"},{"id":2,"name":"David","nickname":"Dave"}]}}""" } "Conversion transformation example 2" in { - val json = toJson(users2).transform { + val json = toJson(users2).transformField { case JField("id", JString(s)) => JField("id", JInt(s.toInt)) case JField("user", x: JObject) => JField("user", JArray(x :: Nil)) } @@ -58,7 +54,7 @@ object XmlExamples extends Specification("XML Examples") { val printer = new scala.xml.PrettyPrinter(100,2) val lotto: JObject = LottoExample.json - val xml = toXml(lotto.transform { + val xml = toXml(lotto.transformField { case JField("winning-numbers", JArray(nums)) => JField("winning-numbers", flattenArray(nums)) case JField("numbers", JArray(nums)) => JField("numbers", flattenArray(nums)) }) @@ -152,11 +148,11 @@ object XmlExamples extends Specification("XML Examples") { // default conversion rules. The transformation function 'attrToObject' makes following conversion: // { ..., "fieldName": "", "attrName":"someValue", ...} -> // { ..., "fieldName": { "attrName": f("someValue") }, ... } - def attrToObject(fieldName: String, attrName: String, f: JString => JValue)(json: JValue) = json.transform { - case JField(n, v: JString) if n == attrName => JObject(JField(n, f(v)) :: Nil) - case JField(n, JString("")) if n == fieldName => JNothing - } transform { - case JField(n, x: JObject) if n == attrName => JField(fieldName, x) + def attrToObject(fieldName: String, attrName: String, f: JString => JValue)(json: JValue) = json.transformField { + case (n, v: JString) if n == attrName => JField(fieldName, JObject(JField(n, f(v)) :: Nil)) + case (n, JString("")) if n == fieldName => JField(n, JNothing) + } transformField { + case (n, x: JObject) if n == attrName => JField(fieldName, x) } "Example with multiple attributes, multiple nested elements " in { From fb4a0ef2e5542e9f8d9659cc1098db2a48b02081 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 14 May 2014 23:21:24 -0400 Subject: [PATCH 2/5] Fix version reference for lift-json migration info. We now know that 2.6 will be the last 2.x release, and that the JField +2.6 -> ------------------------------------------------- JField is no longer a JValue. This means more type safety since it is no longer possible From 1065431ee317f8ba75a79dc86d56fa4c25ca2eb6 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 14 May 2014 23:22:37 -0400 Subject: [PATCH 3/5] Improve formatting for readability in JsonAST. --- .../main/scala/net/liftweb/json/JsonAST.scala | 16 +++++++++++++--- .../src/main/scala/net/liftweb/json/Xml.scala | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index d44ee27b1d..bb4a460648 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -80,7 +80,11 @@ object JsonAST { def find(json: JValue): List[JField] = json match { case JObject(l) => l.foldLeft(List[JField]()) { case (a, (name, value)) => - if (name == nameToFind) a ::: List((name, value)) ::: find(value) else a ::: find(value) + if (name == nameToFind) { + a ::: List((name, value)) ::: find(value) + } else { + a ::: find(value) + } } case JArray(l) => l.foldLeft(List[JField]())((a, json) => a ::: find(json)) case _ => Nil @@ -151,8 +155,14 @@ object JsonAST { def rec(acc: A, v: JValue) = { val newAcc = f(acc, v) v match { - case JObject(l) => l.foldLeft(newAcc) { case (a, (name, value)) => value.fold(a)(f) } - case JArray(l) => l.foldLeft(newAcc)((a, e) => e.fold(a)(f)) + case JObject(l) => + l.foldLeft(newAcc) { + case (a, (name, value)) => value.fold(a)(f) + } + case JArray(l) => + l.foldLeft(newAcc) { (a, e) => + e.fold(a)(f) + } case _ => newAcc } } diff --git a/core/json/src/main/scala/net/liftweb/json/Xml.scala b/core/json/src/main/scala/net/liftweb/json/Xml.scala index 8f22693bc7..0f407a5858 100644 --- a/core/json/src/main/scala/net/liftweb/json/Xml.scala +++ b/core/json/src/main/scala/net/liftweb/json/Xml.scala @@ -111,7 +111,7 @@ object Xml { } case XNode(xs) => JObject(mkFields(xs)) case XArray(elems) => JArray(elems.map(toJValue)) - } + } def mkFields(xs: List[(String, XElem)]) = xs.flatMap { case (name, value) => (value, toJValue(value)) match { @@ -145,7 +145,7 @@ object Xml { case List(x @ XLeaf(_, _ :: _)) => toJValue(x) case List(x) => JObject(JField(nameOf(xml.head), toJValue(x)) :: Nil) case x => JArray(x.map(toJValue)) - } + } } /** Convert given JSON to XML. From a513ab85a5fe8517d8a3173d841b9dc5d49abcac Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 8 Jun 2014 23:40:17 -0400 Subject: [PATCH 4/5] Make JField a proper case class again. Notably it does NOT extend JValue, but it is not simply a tuple either. Also sundry compilation fixes to make everything work well and proper. --- .../scala/net/liftweb/json/scalaz/Base.scala | 2 +- .../scala/net/liftweb/json/scalaz/Base.scala | 2 +- .../main/scala/net/liftweb/json/Diff.scala | 6 +- .../scala/net/liftweb/json/Extraction.scala | 14 ++--- .../main/scala/net/liftweb/json/JsonAST.scala | 57 +++++++++---------- .../scala/net/liftweb/json/JsonParser.scala | 12 ++-- .../main/scala/net/liftweb/json/Merge.scala | 6 +- .../src/main/scala/net/liftweb/json/Xml.scala | 6 +- .../scala/net/liftweb/json/Examples.scala | 4 +- .../scala/net/liftweb/json/JsonAstSpec.scala | 2 +- .../scala/net/liftweb/json/XmlExamples.scala | 6 +- .../scala/net/liftweb/mapper/MetaMapper.scala | 9 +-- .../net/liftweb/record/RecordHelpers.scala | 1 - .../net/liftweb/http/rest/RestHelper.scala | 8 --- 14 files changed, 62 insertions(+), 73 deletions(-) diff --git a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala b/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala index 784f8cbe1f..a8019d325b 100644 --- a/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala +++ b/core/json-scalaz/src/main/scala/net/liftweb/json/scalaz/Base.scala @@ -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): _*) } } diff --git a/core/json-scalaz7/src/main/scala/net/liftweb/json/scalaz/Base.scala b/core/json-scalaz7/src/main/scala/net/liftweb/json/scalaz/Base.scala index d252ae58f1..5441c92c64 100644 --- a/core/json-scalaz7/src/main/scala/net/liftweb/json/scalaz/Base.scala +++ b/core/json-scalaz7/src/main/scala/net/liftweb/json/scalaz/Base.scala @@ -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): _*) } } diff --git a/core/json/src/main/scala/net/liftweb/json/Diff.scala b/core/json/src/main/scala/net/liftweb/json/Diff.scala index 0284826338..91ebabcf88 100644 --- a/core/json/src/main/scala/net/liftweb/json/Diff.scala +++ b/core/json/src/main/scala/net/liftweb/json/Diff.scala @@ -34,7 +34,7 @@ case class Diff(changed: JValue, added: JValue, deleted: JValue) { private[json] def toField(name: String): Diff = { def applyTo(x: JValue) = x match { case JNothing => JNothing - case _ => JObject((name, x)) + case _ => JObject(JField(name, x)) } Diff(applyTo(changed), applyTo(added), applyTo(deleted)) } @@ -66,9 +66,9 @@ object Diff { private def diffFields(vs1: List[JField], vs2: List[JField]) = { def diffRec(xleft: List[JField], yleft: List[JField]): Diff = xleft match { case Nil => Diff(JNothing, if (yleft.isEmpty) JNothing else JObject(yleft), JNothing) - case x :: xs => yleft find (_._1 == x._1) match { + case x :: xs => yleft find (_.name == x.name) match { case Some(y) => - val Diff(c1, a1, d1) = diff(x._2, y._2).toField(y._1) + 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 merge c2, a1 merge a2, d1 merge d2) case None => diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index ac0782e65b..6c93ca94ac 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -101,7 +101,7 @@ object Extraction { .getOrElse(JField(n, JNothing)) } } getOrElse Nil - val uniqueFields = fields filterNot (f => args.find(_._1 == f._1).isDefined) + val uniqueFields = fields filterNot (f => args.find(_.name == f.name).isDefined) mkObject(x.getClass, uniqueFields ++ args) } } @@ -120,7 +120,7 @@ object Extraction { case JDouble(num) => Map(path -> num.toString) case JInt(num) => Map(path -> num.toString) case JBool(value) => Map(path -> value.toString) - case JObject(obj) => obj.foldLeft(Map[String, String]()) { case (map, (name, value)) => + 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 { @@ -209,7 +209,7 @@ object Extraction { if (constructor.choices.size == 1) constructor.choices.head // optimized common case else { val argNames = json match { - case JObject(fs) => fs.map(_._1) + case JObject(fs) => fs.map(_.name) case x => Nil } constructor.bestMatching(argNames) @@ -267,7 +267,7 @@ object Extraction { } def mkWithTypeHint(typeHint: String, fields: List[JField], typeInfo: TypeInfo) = { - val obj = JObject(fields filterNot (_._1 == formats.typeHintFieldName)) + val obj = JObject(fields filterNot (_.name == formats.typeHintFieldName)) val deserializer = formats.typeHints.deserialize if (!deserializer.isDefinedAt(typeHint, obj)) { val concreteClass = formats.typeHints.classFor(typeHint) getOrElse fail("Do not know how to deserialize '" + typeHint + "'") @@ -290,9 +290,9 @@ object Extraction { def unapply(fs: List[JField]): Option[(String, List[JField])] = if (formats.typeHints == NoTypeHints) None else { - val grouped = fs groupBy (_._1 == formats.typeHintFieldName) + val grouped = fs groupBy (_.name == formats.typeHintFieldName) if (grouped.isDefinedAt(true)) - Some((grouped(true).head._2.values.toString, grouped.get(false).getOrElse(Nil))) + Some((grouped(true).head.value.values.toString, grouped.get(false).getOrElse(Nil))) else None } } @@ -324,7 +324,7 @@ object Extraction { else if (classOf[Seq[_]].isAssignableFrom(c)) newCollection(root, m, a => List(a: _*)) else fail("Expected collection but got " + m + " for class " + c) case Dict(m) => root match { - case JObject(xs) => Map(xs.map(x => (x._1, build(x._2, m))): _*) + case JObject(xs) => Map(xs.map(x => (x.name, build(x.value, m))): _*) case x => fail("Expected object but got " + x) } } diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index d9509d9e1a..6f1001757e 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -55,19 +55,19 @@ object JsonAST { } private def findDirectByName(xs: List[JValue], name: String): List[JValue] = xs.flatMap { - case JObject(l) => l.filter { - case (n, _) if n == name => true - case _ => false - } map (_._2) + case JObject(l) => + l.collect { + case JField(n, value) if n == name => value + } case JArray(l) => findDirectByName(l, name) case _ => Nil } private def findDirect(xs: List[JValue], p: JValue => Boolean): List[JValue] = xs.flatMap { - case JObject(l) => l.filter { - case (n, x) if p(x) => true - case _ => false - } map (_._2) + case JObject(l) => + l.collect { + case JField(n, x) if p(x) => x + } case JArray(l) => findDirect(l, p) case x if p(x) => x :: Nil case _ => Nil @@ -82,9 +82,9 @@ object JsonAST { def \\(nameToFind: String): JValue = { def find(json: JValue): List[JField] = json match { case JObject(l) => l.foldLeft(List[JField]()) { - case (a, (name, value)) => + case (a, JField(name, value)) => if (name == nameToFind) { - a ::: List((name, value)) ::: find(value) + a ::: List(JField(name, value)) ::: find(value) } else { a ::: find(value) } @@ -93,7 +93,7 @@ object JsonAST { case _ => Nil } find(this) match { - case (_, x) :: Nil => x + case JField(_, x) :: Nil => x case xs => JObject(xs) } } @@ -146,7 +146,7 @@ object JsonAST { *
*/ def children: List[JValue] = this match { - case JObject(l) => l map (_._2) + case JObject(l) => l map (_.value) case JArray(l) => l case _ => Nil } @@ -160,7 +160,7 @@ object JsonAST { v match { case JObject(l) => l.foldLeft(newAcc) { - case (a, (name, value)) => value.fold(a)(f) + case (a, JField(name, value)) => value.fold(a)(f) } case JArray(l) => l.foldLeft(newAcc) { (a, e) => @@ -179,7 +179,7 @@ object JsonAST { def rec(acc: A, v: JValue) = { v match { case JObject(l) => l.foldLeft(acc) { - case (a, field@(name, value)) => value.foldField(f(a, field))(f) + case (a, field@JField(name, value)) => value.foldField(f(a, field))(f) } case JArray(l) => l.foldLeft(acc)((a, e) => e.foldField(a)(f)) case _ => acc @@ -200,7 +200,7 @@ object JsonAST { */ def map(f: JValue => JValue): JValue = { def rec(v: JValue): JValue = v match { - case JObject(l) => f(JObject(l.map { case (n, v) => (n, rec(v)) })) + case JObject(l) => f(JObject(l.map { field => field.copy(value = rec(field.value)) })) case JArray(l) => f(JArray(l.map(rec))) case x => f(x) } @@ -219,7 +219,7 @@ object JsonAST { */ def mapField(f: JField => JField): JValue = { def rec(v: JValue): JValue = v match { - case JObject(l) => JObject(l.map { case (n, v) => f(n, rec(v)) }) + case JObject(l) => JObject(l.map { field => f(field.copy(value = rec(field.value))) }) case JArray(l) => JArray(l.map(rec)) case x => x } @@ -288,7 +288,7 @@ object JsonAST { def findField(p: JField => Boolean): Option[JField] = { def find(json: JValue): Option[JField] = json match { case JObject(fs) if (fs find p).isDefined => return fs find p - case JObject(fs) => fs.flatMap { case (n, v) => find(v) }.headOption + case JObject(fs) => fs.flatMap { case JField(n, v) => find(v) }.headOption case JArray(l) => l.flatMap(find _).headOption case _ => None } @@ -305,7 +305,7 @@ object JsonAST { def find(json: JValue): Option[JValue] = { if (p(json)) return Some(json) json match { - case JObject(fs) => fs.flatMap { case (n, v) => find(v) }.headOption + case JObject(fs) => fs.flatMap { case JField(n, v) => find(v) }.headOption case JArray(l) => l.flatMap(find _).headOption case _ => None } @@ -376,7 +376,7 @@ object JsonAST { *
*/ def removeField(p: JField => Boolean): JValue = this mapField { - case x if p(x) => (x._1, JNothing) + case x if p(x) => JField(x.name, JNothing) case x => x } @@ -478,11 +478,12 @@ object JsonAST { case class JObject(obj: List[JField]) extends JValue { type Values = Map[String, Any] - def values = + def values = { obj.map { - case (n, v) => - (n, v.values) + case JField(name, value) => + (name, value.values): (String, Any) }.toMap + } override def equals(that: Any): Boolean = that match { case o: JObject => obj.toSet == o.obj.toSet @@ -501,11 +502,7 @@ object JsonAST { override def apply(i: Int): JValue = arr(i) } - type JField = (String, JValue) - object JField { - def apply(name: String, value: JValue) = (name, value) - def unapply(f: JField): Option[(String, JValue)] = Some(f) - } + case class JField(name: String, value: JValue) /** Renders JSON. * @see Printer#compact @@ -522,13 +519,13 @@ object JsonAST { case JString(s) => text("\"" + quote(s) + "\"") case JArray(arr) => text("[") :: series(trimArr(arr).map(render)) :: text("]") case JObject(obj) => - val nested = break :: fields(trimObj(obj).map { case (name, value) => text("\"" + quote(name) + "\":") :: render(value) }) + val nested = break :: fields(trimObj(obj).map { case JField(name, value) => text("\"" + quote(name) + "\":") :: render(value) }) text("{") :: nest(2, nested) :: break :: text("}") case JNothing => sys.error("can't render 'nothing'") //TODO: this should not throw an exception } private def trimArr(xs: List[JValue]) = xs.filter(_ != JNothing) - private def trimObj(xs: List[JField]) = xs.filter(_._2 != JNothing) + private def trimObj(xs: List[JField]) = xs.filter(_.value != JNothing) private def series(docs: List[Document]) = punctuate(text(","), docs) private def fields(docs: List[Document]) = punctuate(text(",") :: break, docs) @@ -608,7 +605,7 @@ object JsonAST { buf.append("{") //open bracket if (!xs.isEmpty) { xs.foreach { - case (name, value) if value != JNothing => + case JField(name, value) if value != JNothing => bufQuote(name, buf) buf.append(":") bufRender(value, buf) diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index b4f1d717a2..953f32de68 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -145,7 +145,7 @@ object JsonParser { // This is a slightly faster way to correct order of fields and arrays than using 'map'. def reverse(v: JValue): JValue = v match { - case JObject(l) => JObject((l.map { case (n, v) => (n, reverse(v)) }).reverse) + case JObject(l) => JObject((l.map { field => field.copy(value = reverse(field.value)) }).reverse) case JArray(l) => JArray(l.map(reverse).reverse) case x => x } @@ -157,10 +157,10 @@ object JsonParser { } vals.peekOption match { - case Some((name: String, value)) => + case Some(JField(name: String, value)) => vals.pop(classOf[JField]) val obj = vals.peek(classOf[JObject]) - vals.replace(JObject((name, toJValue(v)) :: obj.obj)) + vals.replace(JObject(JField(name, toJValue(v)) :: obj.obj)) case Some(o: JObject) => vals.replace(JObject(vals.peek(classOf[JField]) :: o.obj)) case Some(a: JArray) => vals.replace(JArray(toJValue(v) :: a.arr)) @@ -172,12 +172,12 @@ object JsonParser { def newValue(v: JValue) { if (!vals.isEmpty) vals.peekAny match { - case (name: String, value) => + case JField(name, value) => vals.pop(classOf[JField]) val obj = vals.peek(classOf[JObject]) - vals.replace(JObject((name, v) :: obj.obj)) + vals.replace(JObject(JField(name, v) :: obj.obj)) case a: JArray => vals.replace(JArray(v :: a.arr)) - case _ => p.fail("expected field or array") + case other => p.fail("expected field or array but got " + other) } else { vals.push(v) root = Some(v) diff --git a/core/json/src/main/scala/net/liftweb/json/Merge.scala b/core/json/src/main/scala/net/liftweb/json/Merge.scala index 6cca0d759a..f1fb6edc65 100644 --- a/core/json/src/main/scala/net/liftweb/json/Merge.scala +++ b/core/json/src/main/scala/net/liftweb/json/Merge.scala @@ -68,9 +68,9 @@ object Merge { private[json] def mergeFields(vs1: List[JField], vs2: List[JField]): List[JField] = { def mergeRec(xleft: List[JField], yleft: List[JField]): List[JField] = xleft match { case Nil => yleft - case (xn, xv) :: xs => yleft find (_._1 == xn) match { - case Some(y @ (yn, yv)) => - (xn, merge(xv, yv)) :: mergeRec(xs, yleft filterNot (_ == y)) + case JField(xn, xv) :: xs => yleft find (_.name == xn) match { + case Some(y @ JField(yn, yv)) => + JField(xn, merge(xv, yv)) :: mergeRec(xs, yleft filterNot (_ == y)) case None => JField(xn, xv) :: mergeRec(xs, yleft) } } diff --git a/core/json/src/main/scala/net/liftweb/json/Xml.scala b/core/json/src/main/scala/net/liftweb/json/Xml.scala index d3bba05c17..209a752f4b 100644 --- a/core/json/src/main/scala/net/liftweb/json/Xml.scala +++ b/core/json/src/main/scala/net/liftweb/json/Xml.scala @@ -107,7 +107,7 @@ object Xml { case XLeaf((name, value), attrs) => (value, attrs) match { case (_, Nil) => toJValue(value) case (XValue(""), xs) => JObject(mkFields(xs)) - case (_, xs) => JObject((name, toJValue(value)) :: mkFields(xs)) + case (_, xs) => JObject(JField(name, toJValue(value)) :: mkFields(xs)) } case XNode(xs) => JObject(mkFields(xs)) case XArray(elems) => JArray(elems.map(toJValue)) @@ -169,7 +169,7 @@ object Xml { */ def toXml(json: JValue): NodeSeq = { def toXml(name: String, json: JValue): NodeSeq = json match { - case JObject(fields) => new XmlNode(name, fields flatMap { case (n, v) => toXml(n, v) }) + case JObject(fields) => new XmlNode(name, fields flatMap { case JField(n, v) => toXml(n, v) }) case JArray(xs) => xs flatMap { v => toXml(name, v) } case JInt(x) => new XmlElem(name, x.toString) case JDouble(x) => new XmlElem(name, x.toString) @@ -180,7 +180,7 @@ object Xml { } json match { - case JObject(fields) => fields flatMap { case (name, value) => toXml(name, value) } + case JObject(fields) => fields flatMap { case JField(name, value) => toXml(name, value) } case x => toXml("root", x) } } diff --git a/core/json/src/test/scala/net/liftweb/json/Examples.scala b/core/json/src/test/scala/net/liftweb/json/Examples.scala index ce9726ec85..b1191175a1 100644 --- a/core/json/src/test/scala/net/liftweb/json/Examples.scala +++ b/core/json/src/test/scala/net/liftweb/json/Examples.scala @@ -120,13 +120,13 @@ trait AbstractExamples extends Specification { } "JSON building example" in { - val json = JObject(("name", JString("joe")), ("age", JInt(34))) ++ JObject(("name", ("mazy")), ("age", JInt(31))) + val json = JObject(JField("name", JString("joe")), JField("age", JInt(34))) ++ JObject(JField("name", ("mazy")), JField("age", JInt(31))) print(json) mustEqual """[{"name":"joe","age":34},{"name":"mazy","age":31}]""" } "JSON building with implicit primitive conversions example" in { import Implicits._ - val json = JObject(("name", "joe"), ("age", 34)) ++ JObject(("name", "mazy"), ("age", 31)) + val json = JObject(JField("name", "joe"), JField("age", 34)) ++ JObject(JField("name", "mazy"), JField("age", 31)) print(json) mustEqual """[{"name":"joe","age":34},{"name":"mazy","age":31}]""" } diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala index 22dca7f6e9..f2bf144bf7 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala @@ -107,7 +107,7 @@ object JsonAstSpec extends Specification with JValueGen with ScalaCheck { val anyReplacement = (x: JValue, replacement: JObject) => { def findOnePath(jv: JValue, l: List[String]): List[String] = jv match { case JObject(fl) => fl match { - case field :: xs => findOnePath(field._2, l) + case field :: xs => findOnePath(field.value, l) case Nil => l } case _ => l diff --git a/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala b/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala index 818044f66b..09e026fb28 100644 --- a/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/XmlExamples.scala @@ -150,10 +150,10 @@ object XmlExamples extends Specification { // { ..., "fieldName": "", "attrName":"someValue", ...} -> // { ..., "fieldName": { "attrName": f("someValue") }, ... } def attrToObject(fieldName: String, attrName: String, f: JString => JValue)(json: JValue) = json.transformField { - case (n, v: JString) if n == attrName => JField(fieldName, JObject(JField(n, f(v)) :: Nil)) - case (n, JString("")) if n == fieldName => JField(n, JNothing) + case JField(n, v: JString) if n == attrName => JField(fieldName, JObject(JField(n, f(v)) :: Nil)) + case JField(n, JString("")) if n == fieldName => JField(n, JNothing) } transformField { - case (n, x: JObject) if n == attrName => JField(fieldName, x) + case JField(n, x: JObject) if n == attrName => JField(fieldName, x) } "Example with multiple attributes, multiple nested elements " in { diff --git a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala index 38262493f8..b2e75ecffb 100644 --- a/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala +++ b/persistence/mapper/src/main/scala/net/liftweb/mapper/MetaMapper.scala @@ -674,10 +674,11 @@ trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] { import JsonAST._ ret.runSafe { - for { - field <- json.obj - JField("$persisted", JBool(per)) <- field - } ret.persisted_? = per + json.findField { + case JField("$persisted", JBool(per)) => + ret.persisted_? = per + true + } for { field <- json.obj diff --git a/persistence/record/src/main/scala/net/liftweb/record/RecordHelpers.scala b/persistence/record/src/main/scala/net/liftweb/record/RecordHelpers.scala index f565f058bb..fe566947f9 100644 --- a/persistence/record/src/main/scala/net/liftweb/record/RecordHelpers.scala +++ b/persistence/record/src/main/scala/net/liftweb/record/RecordHelpers.scala @@ -29,7 +29,6 @@ object RecordHelpers { case JArray(vs) => JsArray(vs.map(jvalueToJsExp): _*) case JBool(b) => if (b) JsTrue else JsFalse case JDouble(d) => Num(d) - case JField(n,v) => sys.error("no parallel") case JInt(i) => Num(i) case JNothing => JsNull case JNull => JsNull diff --git a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala index d9683f1a1a..559d847297 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala @@ -705,19 +705,11 @@ trait RestHelper extends LiftRules.DispatchPF { original match { case JObject(fields) => toMerge match { - case jf: JField => JObject(replace(fields, jf)) case JObject(otherFields) => JObject(otherFields.foldLeft(fields)(replace(_, _))) case _ => original } - case jf: JField => - toMerge match { - case jfMerge: JField => JObject(replace(List(jf), jfMerge)) - case JObject(otherFields) => - JObject(otherFields.foldLeft(List(jf))(replace(_, _))) - case _ => original - } case _ => original // can't merge } } From 17a17cb813411291cb1e5c284cd96f350c4dea9c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 8 Jun 2014 23:48:18 -0400 Subject: [PATCH 5/5] Fix docs for case class JField. --- core/json/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/json/README.md b/core/json/README.md index cad918fd81..aeca5c7041 100644 --- a/core/json/README.md +++ b/core/json/README.md @@ -13,7 +13,7 @@ a JSON document as a syntax tree. case class JObject(obj: List[JField]) extends JValue case class JArray(arr: List[JValue]) extends JValue - type JField = (String, 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 @@ -118,7 +118,7 @@ Any valid json can be parsed into internal AST format. scala> import net.liftweb.json._ scala> parse(""" { "numbers" : [1, 2, 3, 4] } """) res0: net.liftweb.json.JsonAST.JValue = - JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) + JObject(List(JField(numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) Producing JSON ============== @@ -262,7 +262,7 @@ Please see more examples in src/test/scala/net/liftweb/json/MergeExamples.scala changed: net.liftweb.json.JsonAST.JValue = JNothing added: net.liftweb.json.JsonAST.JValue = JNothing deleted: net.liftweb.json.JsonAST.JValue = JObject(List((lotto,JObject(List((winners, - JArray(List(JObject(List((winner-id,JInt(54)), (numbers,JArray( + 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)))))))))))))) @@ -342,7 +342,7 @@ Json AST can be queried using XPath like functions. Following REPL session shows scala> json \\ "spouse" res0: net.liftweb.json.JsonAST.JValue = JObject(List( - (person,JObject(List((name,JString(Marilyn)), (age,JInt(33))))))) + JField(person,JObject(List((name,JString(Marilyn)), (age,JInt(33))))))) scala> compact(render(res0)) res1: String = {"person":{"name":"Marilyn","age":33}} @@ -363,20 +363,20 @@ Json AST can be queried using XPath like functions. Following REPL session shows case JField("name", _) => true case _ => false } - res6: Option[net.liftweb.json.JsonAST.JValue] = Some((name,JString(Joe))) + res6: Option[net.liftweb.json.JsonAST.JField] = Some(JField(name,JString(Joe))) 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 transformField { case ("name", JString(s)) => ("NAME", JString(s.toUpperCase)) } - res8: net.liftweb.json.JsonAST.JValue = JObject(List((person,JObject(List( - (NAME,JString(JOE)), (age,JInt(35)), (spouse,JObject(List( - (person,JObject(List((NAME,JString(MARILYN)), (age,JInt(33))))))))))))) + res8: net.liftweb.json.JsonAST.JValue = JObject(List(JField(person,JObject(List( + JField(NAME,JString(JOE)), JField(age,JInt(35)), JField(spouse,JObject(List( + JField(person,JObject(List(JField(NAME,JString(MARILYN)), JField(age,JInt(33))))))))))))) scala> json.values res8: scala.collection.immutable.Map[String,Any] = Map(person -> Map(name -> Joe, age -> 35, spouse -> Map(person -> Map(name -> Marilyn, age -> 33)))) @@ -399,7 +399,7 @@ Indexed path expressions work too and values can be unboxed using type expressio """) scala> (json \ "children")(0) - res0: net.liftweb.json.JsonAST.JValue = JObject(List((name,JString(Mary)), (age,JInt(5)))) + res0: net.liftweb.json.JsonAST.JValue = JObject(List(JField(name,JString(Mary)), JField(age,JInt(5)))) scala> (json \ "children")(1) \ "name" res1: net.liftweb.json.JsonAST.JValue = JString(Mazy) @@ -459,7 +459,7 @@ Use transform function to postprocess AST. scala> case class Person(firstname: String) scala> json transformField { - case ("first-name", x) => ("firstname", x) + case JField("first-name", x) => ("firstname", x) } Extraction function tries to find the best matching constructor when case class has auxiliary @@ -664,8 +664,8 @@ XML document which happens to have just one user-element will generate a JSON do is rarely a desired outcome. These both problems can be fixed by following transformation function. scala> json transformField { - case ("id", JString(s)) => ("id", JInt(s.toInt)) - case ("user", x: JObject) => ("user", JArray(x :: Nil)) + 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: