All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.liftweb.json.JsonAST.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009-2013 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.liftweb
package json

import scala.language.implicitConversions

object JsonAST {
  import scala.text.{Document, DocText}
  import scala.text.Document._

  /** Concatenates a sequence of JValues.
    * 

* Example:

    * concat(JInt(1), JInt(2)) == JArray(List(JInt(1), JInt(2)))
    * 
*/ def concat(xs: JValue*) = xs.foldLeft(JNothing: JValue)(_ ++ _) object JValue extends Merge.Mergeable /** * Data type for Json AST. */ sealed abstract class JValue extends Diff.Diffable { type Values /** XPath-like expression to query JSON fields by name. Matches only fields on * next level. *

* Example:

      * 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 { case Nil => JNothing case JField(_, x) :: Nil => x case x :: Nil => x case x => JArray(x) } } 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 _ => false } case JArray(l) => findDirect(l, p) case x if p(x) => x :: Nil case _ => Nil } /** XPath-like expression to query JSON fields by name. Returns all matching fields. *

* Example:

      * json \\ "name"
      * 
*/ 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 _ => Nil } find(this) match { case JField(_, x) :: Nil => x case x :: Nil => x case x => JObject(x) } } /** XPath-like expression to query JSON fields by type. Matches only fields on * next level. *

* Example:

      * json \ classOf[JInt]
      * 
*/ def \[A <: JValue](clazz: Class[A]): List[A#Values] = findDirect(children, typePredicate(clazz) _).asInstanceOf[List[A]] map { _.values } /** XPath-like expression to query JSON fields by type. Returns all matching fields. *

* Example:

      * json \\ classOf[JInt]
      * 
*/ def \\[A <: JValue](clazz: Class[A]): List[A#Values] = (this filter typePredicate(clazz) _).asInstanceOf[List[A]] map { _.values } private def typePredicate[A <: JValue](clazz: Class[A])(json: JValue) = json match { case x if x.getClass == clazz => true case _ => false } /** Return nth element from JSON. * Meaningful only to JArray, JObject and JField. Returns JNothing for other types. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil)(1) == JInt(2)
      * 
*/ def apply(i: Int): JValue = JNothing /** Return unboxed values from JSON *

* Example:

      * JObject(JField("name", JString("joe")) :: Nil).values == Map("name" -> "joe")
      * 
*/ def values: Values /** Return direct child elements. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil).children == List(JInt(1), JInt(2))
      * 
*/ def children = this match { case JObject(l) => l case JArray(l) => l case JField(n, v) => List(v) case _ => Nil } /** Return a combined value by folding over JSON by applying a function f * for each element. The initial value is z. */ def fold[A](z: A)(f: (A, JValue) => A): A = { 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 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 new JValue resulting from applying the given function f * to each element in JSON. *

* Example:

      * 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 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 partial function f * to each element in JSON. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil) transform { case JInt(x) => JInt(x+1) }
      * 
*/ def transform(f: PartialFunction[JValue, JValue]): JValue = map { x => if (f.isDefinedAt(x)) f(x) else x } /** Return a new JValue resulting from replacing the value at the specified field * path with the replacement value provided. This has no effect if the path is empty * or if the value is not a JObject instance. *

* Example:

      * JObject(List(JField("foo", JObject(List(JField("bar", JInt(1))))))).replace("foo" :: "bar" :: Nil, JString("baz"))
      * // returns JObject(List(JField("foo", JObject(List(JField("bar", JString("baz")))))))
      * 
*/ def replace(l: List[String], replacement: JValue): JValue = { def rep(l: List[String], in: JValue): JValue = { l match { case x :: xs => in match { case JObject(fields) => JObject( fields.map { case JField(`x`, value) => JField(x, if (xs == Nil) replacement else rep(xs, value)) case field => field } ) case other => other } case Nil => in } } rep(l, this) } /** Return the first element from JSON which matches the given predicate. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil) find { _ == JInt(2) } == Some(JInt(2))
      * 
*/ 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 _).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. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil) filter { case JInt(x) => x > 1; case _ => false }
      * 
*/ def filter(p: JValue => Boolean): List[JValue] = fold(List[JValue]())((acc, e) => if (p(e)) e :: acc else acc).reverse /** * To make 2.10 happy */ def withFilter(p: JValue => Boolean) = new WithFilter(this, p) final class WithFilter(self: JValue, p: JValue => Boolean) { def map[A](f: JValue => A): List[A] = self filter p map f def flatMap[A](f: JValue => List[A]) = self filter p flatMap f def withFilter(q: JValue => Boolean): WithFilter = new WithFilter(self, x => p(x) && q(x)) def foreach[U](f: JValue => U): Unit = self filter p foreach f } /** Concatenate with another JSON. * This is a concatenation monoid: (JValue, ++, JNothing) *

* Example:

      * JArray(JInt(1) :: JInt(2) :: Nil) ++ JArray(JInt(3) :: Nil) ==
      * JArray(List(JInt(1), JInt(2), JInt(3)))
      * 
*/ def ++(other: JValue) = { 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. *

* Example:

      * JArray(JInt(1) :: JInt(2) :: JNull :: Nil) remove { _ == JNull }
      * 
*/ def remove(p: JValue => Boolean): JValue = this map { case x if p(x) => JNothing case x => x } /** Extract a value from a JSON. *

* Value can be: *

    *
  • case class
  • *
  • primitive (String, Boolean, Date, etc.)
  • *
  • supported collection type (List, Seq, Map[String, _], Set)
  • *
  • any type which has a configured custom deserializer
  • *
*

* Example:

      * case class Person(name: String)
      * JObject(JField("name", JString("joe")) :: Nil).extract[Person] == Person("joe")
      * 
*/ def extract[A](implicit formats: Formats, mf: scala.reflect.Manifest[A]): A = Extraction.extract(this)(formats, mf) /** Extract a value from a JSON. *

* Value can be: *

    *
  • case class
  • *
  • primitive (String, Boolean, Date, etc.)
  • *
  • supported collection type (List, Seq, Map[String, _], Set)
  • *
  • any type which has a configured custom deserializer
  • *
*

* Example:

      * case class Person(name: String)
      * JObject(JField("name", JString("joe")) :: Nil).extractOpt[Person] == Some(Person("joe"))
      * 
*/ def extractOpt[A](implicit formats: Formats, mf: scala.reflect.Manifest[A]): Option[A] = Extraction.extractOpt(this)(formats, mf) /** Extract a value from a JSON using a default value. *

* Value can be: *

    *
  • case class
  • *
  • primitive (String, Boolean, Date, etc.)
  • *
  • supported collection type (List, Seq, Map[String, _], Set)
  • *
  • any type which has a configured custom deserializer
  • *
*

* Example:

      * case class Person(name: String)
      * JNothing.extractOrElse(Person("joe")) == Person("joe")
      * 
*/ def extractOrElse[A](default: => A)(implicit formats: Formats, mf: scala.reflect.Manifest[A]): A = Extraction.extractOpt(this)(formats, mf).getOrElse(default) def toOpt: Option[JValue] = this match { case JNothing => None case json => Some(json) } } case object JNothing extends JValue { type Values = None.type def values = None } 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 : (String, Any)) override def equals(that: Any): Boolean = that match { case o: JObject => obj.toSet == o.obj.toSet case _ => false } override def hashCode = obj.toSet[JField].hashCode } 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) } /** Renders JSON. * @see Printer#compact * @see Printer#pretty */ def render(value: JValue): Document = value match { case null => text("null") 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 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("\"" + quote(n) + "\":") :: render(v) case JObject(obj) => val nested = break :: fields(trimObj(obj).map(f => text("\"" + quote(f.name) + "\":") :: render(f.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(_.value != JNothing) private def series(docs: List[Document]) = punctuate(text(","), docs) private def fields(docs: List[Document]) = punctuate(text(",") :: break, docs) private def punctuate(p: Document, docs: List[Document]): Document = if (docs.length == 0) empty else docs.reduceLeft((d1, d2) => d1 :: p :: d2) private[json] def quote(s: String): String = { val buf = new StringBuilder appendEscapedString(buf, s) buf.toString } private def appendEscapedString(buf: StringBuilder, s: String) { for (i <- 0 until s.length) { val c = s.charAt(i) buf.append(c match { case '"' => "\\\"" case '\\' => "\\\\" case '\b' => "\\b" case '\f' => "\\f" case '\n' => "\\n" case '\r' => "\\r" case '\t' => "\\t" case c if ((c >= '\u0000' && c < '\u0020')) => "\\u%04x".format(c: Int) case c => c }) } } /** Renders JSON directly to string in compact format. * This is an optimized version of compact(render(value)) * when the intermediate Document is not needed. */ def compactRender(value: JValue): String = { bufRender(value, new StringBuilder).toString() } /** * * @param value the JSON to render * @param buf the buffer to render the JSON into. may not be empty */ private def bufRender(value: JValue, buf: StringBuilder): StringBuilder = value match { case null => buf.append("null") case JBool(true) => buf.append("true") case JBool(false) => buf.append("false") case JDouble(n) => buf.append(n.toString) case JInt(n) => buf.append(n.toString) case JNull => buf.append("null") case JString(null) => buf.append("null") case JString(s) => bufQuote(s, buf) case JArray(arr) => bufRenderArr(arr, buf) case JField(k, v) => bufQuote(k, buf).append(":"); bufRender(v, buf) case JObject(obj) => bufRenderObj(obj, buf) case JNothing => sys.error("can't render 'nothing'") //TODO: this should not throw an exception } private def bufRenderArr(xs: List[JValue], buf: StringBuilder): StringBuilder = { buf.append("[") //open array if (!xs.isEmpty) { xs.foreach(elem => Option(elem) match { case Some(e) => if (e != JNothing) { bufRender(e, buf) buf.append(",") } case None => buf.append("null,") }) if (buf.last == ',') buf.deleteCharAt(buf.length - 1) //delete last comma } buf.append("]") buf } private def bufRenderObj(xs: List[JField], buf: StringBuilder): StringBuilder = { buf.append("{") //open bracket if (!xs.isEmpty) { xs.foreach(elem => if (elem.value != JNothing) { bufQuote(elem.name, buf) buf.append(":") bufRender(elem.value, buf) buf.append(",") }) if (buf.last == ',') buf.deleteCharAt(buf.length - 1) //delete last comma } buf.append("}") //close bracket buf } private def bufQuote(s: String, buf: StringBuilder): StringBuilder = { buf.append("\"") //open quote appendEscapedString(buf, s) buf.append("\"") //close quote buf } } /** Basic implicit conversions from primitive types into JSON. * Example:
  * import net.liftweb.json.Implicits._
  * JObject(JField("name", "joe") :: Nil) == JObject(JField("name", JString("joe")) :: Nil)
  * 
*/ object Implicits extends Implicits trait Implicits { 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 float2jvalue(x: Float) = JDouble(x) implicit def bigdecimal2jvalue(x: BigDecimal) = JDouble(x.doubleValue) implicit def boolean2jvalue(x: Boolean) = JBool(x) implicit def string2jvalue(x: String) = JString(x) } /** A DSL to produce valid JSON. * Example:
  * import net.liftweb.json.JsonDSL._
  * ("name", "joe") ~ ("age", 15) == JObject(JField("name",JString("joe")) :: JField("age",JInt(15)) :: Nil)
  * 
*/ object JsonDSL extends JsonDSL trait JsonDSL extends Implicits { implicit def seq2jvalue[A <% JValue](s: Traversable[A]) = JArray(s.toList.map { a => val v: JValue = a; v }) implicit def map2jvalue[A <% JValue](m: Map[String, A]) = JObject(m.toList.map { case (k, v) => JField(k, v) }) implicit def option2jvalue[A <% JValue](opt: Option[A]): JValue = opt match { case Some(x) => x case None => JNothing } implicit def symbol2jvalue(x: Symbol) = JString(x.name) 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) } } /** Printer converts JSON to String. * Before printing a JValue needs to be rendered into scala.text.Document. *

* Example:

  * pretty(render(json))
  * 
* * @see net.liftweb.json.JsonAST#render */ object Printer extends Printer trait Printer { import java.io._ import scala.text._ /** Compact printing (no whitespace etc.) */ def compact(d: Document): String = compact(d, new StringWriter).toString /** Compact printing (no whitespace etc.) */ def compact[A <: Writer](d: Document, out: A): A = { def layout(docs: List[Document]): Unit = docs match { case Nil => case DocText(s) :: rs => out.write(s); layout(rs) case DocCons(d1, d2) :: rs => layout(d1 :: d2 :: rs) case DocBreak :: rs => layout(rs) case DocNest(_, d) :: rs => layout(d :: rs) case DocGroup(d) :: rs => layout(d :: rs) case DocNil :: rs => layout(rs) case _ :: rs => layout(rs) } layout(List(d)) out.flush out } /** Pretty printing. */ def pretty(d: Document): String = pretty(d, new StringWriter).toString /** Pretty printing. */ def pretty[A <: Writer](d: Document, out: A): A = { d.format(0, out) out } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy