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

org.jiris.JsonPath.scala Maven / Gradle / Ivy

The newest version!
package org.jiris

import scala.language.dynamics

import play.api.libs.json.Json._
import play.api.libs.json._
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import scala.reflect.runtime.universe._

object JsonPath {
      trait PathNaming {
          def toJsonKey(javaKey: String): String
      }

      object DefaultPathNaming extends PathNaming {
          def toJsonKey(javaKey: String) = javaKey
      }
      
      object DefaultDateTimeReads extends Reads[DateTime] {
          def reads(js: JsValue) = {
              JsSuccess(new DateTime(js.as[String], DateTimeZone.UTC))
          }
      }
      
      object DynaJson {
          def apply(jsValue: JsValue)(implicit ns: PathNaming) = new DynaJson(jsValue)
          def apply(jsValue: Option[JsValue])(implicit ns: PathNaming) = new DynaJson(jsValue)
          def apply(lookupResult: JsLookupResult)(implicit ns: PathNaming) = new DynaJson(lookupResult)
      }
      
      object JpDefined {
          def unapply(doc: DynaJson): Option[JsValue] = {
              doc.lookupResult match {
                  case JsDefined(js) => Some(js)
                  case _ => None
              }
          }
      }
      
      trait JpUndefined
      
      class DynaJson(val lookupResult: JsLookupResult)(implicit ns: PathNaming) extends Dynamic {
          def this(jsValue: JsValue)(implicit ns: PathNaming) = this(new JsDefined(jsValue))
          def this(jsValue: Option[JsValue])(implicit ns: PathNaming) = this(jsValue match {
              case Some(v) => new JsDefined(v)
              case None => new JsUndefined("")
          })
          
          def selectDynamic(key: String) = apply(key)
          
          def applyDynamic(key: String) = new DynaJsonApplyDynamic(this, key)
          
          def apply(key: String): DynaJson = {
              val singleRow = () => lookupResult \ ns.toJsonKey(key) match {
                  case r: JsUndefined => new UndefinedDynaJson(r)
                  case r => new DynaJson(r)
              }

              lookupResult match {
                  case JsDefined(js) => key match {
                      case "*" => js match {
                          case a: JsArray => 
                              // rebuild an array with children
                              val children = a.value.map { e => e match {
                                  case a: JsArray => a.value
                                  case o: JsObject => o.values
                                  case _ => Seq[JsValue]()
                              }}.flatten
                              new DynaJson(JsArray(children))
                          case o: JsObject => new DynaJson(JsArray(o.values.toSeq))
                          case _ => new UndefinedDynaJson(new JsUndefined(""))
                      }
                      case _ => js match {
                          case a: JsArray => 
                              // rebuild an array with children
                              val children = a.value.filter { e =>
                                  e \ ns.toJsonKey(key) match {
                                      case r: JsUndefined => false
                                      case r => true
                                  }
                              }.map { e => (e \ ns.toJsonKey(key)).as[JsValue] }
                              new DynaJson(JsArray(children))
                          case _ => singleRow()
                      }
                  }
                  case _ =>  singleRow()
              }
          }
          
          def apply(filter: JpPathFilter) = filter.find(this)
      
          def apply(index: Int) = index match {
              case -1 => this
              case _ =>
                  lookupResult(index) match {
                      case r: JsUndefined => new UndefinedDynaJson(r)
                      case r => new DynaJson(r)
                  }
          }
          
          def getOrElse(e: => DynaJson) = this match {
              case _: JpUndefined => e
              case _ => this
          }
          
          def jsValue = lookupResult.get
      }
      
      protected class UndefinedDynaJson(lookupResult: JsLookupResult)(implicit ns: PathNaming) extends DynaJson(lookupResult) with JpUndefined {
          override def jsValue = null
      }

      protected class DynaJsonApplyDynamic(path: DynaJson, key: String) {
          def apply(index: Int) = path.apply(key).apply(index)
          
          def apply(filter: JpPathFilter) = path.apply(key).apply(filter)
      }
      
      abstract class JpExpression {
          def &&(r: JpExpression) = new JpAndExpression(this, r)
          def ||(r: JpExpression) = new JpOrExpression(this, r)
          
          def evaluate(filter: JpPathFilter, path: DynaJson)(implicit PathNaming: PathNaming): Seq[JpResult]
      }
      
      protected class JpAndExpression(l: JpExpression, r: JpExpression) extends JpExpression {
          def evaluate(filter: JpPathFilter, path: DynaJson)(implicit PathNaming: PathNaming) = {
              val lKeys = l.evaluate(filter, path).map{ _.key }.toSet
              r.evaluate(filter, path).filter{ result =>
                  lKeys.contains(result.key)
              }
          }
      }
      
      protected class JpOrExpression(l: JpExpression, r: JpExpression) extends JpExpression {
          def evaluate(filter: JpPathFilter, path: DynaJson)(implicit PathNaming: PathNaming) = {
              (l.evaluate(filter, path) ++ r.evaluate(filter, path)).map { result =>
                  result.key -> result
              }.map(e => e._2).toSeq
          }
      }
      
      abstract class JpBinaryExpression[T: Reads](l: JpPathExpression, r: JpConstantExpression[T]) extends JpExpression {
          def evaluate(filter: JpPathFilter, path: DynaJson)(implicit PathNaming: PathNaming) = {
              val toStringOrDouble = (v: Any) => v match {
                  case i: Int => i.toDouble
                  case i: java.lang.Integer => i.toDouble
                  case d: java.lang.Double => d.toDouble
                  case n: JsNumber => n.as[Double]
                  case s: JsString => s.as[String]
                  case d: DateTime => d
                  case other => other.toString
              }
              
              // either double or string
              val normalize = (l: Any, r: Any) => l match {
                case s: String => r match {
                    case r: DateTime => (new JsString(s).as[T].asInstanceOf[DateTime].getMillis.toDouble, 
                                         r.getMillis.toDouble)
                    case r: Double => (s, r.toString)
                    case _ => (s, r)
                }
                case d: Double => r match {
                    case r: String => (d.toString, r)
                    case _ => (d, r)
                }
            }
              
              val rr = toStringOrDouble(r.evaluate(filter, path).head.key)
              l match {
                  case selection: JpPathExpression => 
                      selection.evaluate(filter, path).filter{ result =>
                          val (lvalue, rvalue) = normalize(toStringOrDouble(result.result), rr)
                          
                          rvalue match {
                              case rvalue: Double => 
                                  _evaluate[Double](lvalue.asInstanceOf[Double], rvalue.asInstanceOf[Double])
                              case rvalue: String => 
                                  _evaluate[String](lvalue.asInstanceOf[String], rvalue.asInstanceOf[String])
                          }
                      }
              }
          }
          
          def _evaluate[T: Ordering](l: T, r: T): Boolean
          
          implicit def compare[T: Ordering](l: T, r: T) = implicitly[Ordering[T]].compare(l, r)
      }
      
      protected class JpConstantExpression[T: Reads](v: T) extends JpExpression {
          def evaluate(filter: JpPathFilter, path: DynaJson)(implicit PathNaming: PathNaming) = 
              Seq(new JpResult(v, null, null))
      }
      
      protected case class JpResult(key: Any, doc: JsValue, result: JsValue)
      
      protected class JpPathExpression(paths: Either[String, Int]*) extends JpExpression with Dynamic {
          def <[R: Reads](r: JpConstantExpression[R]) = new JpBinaryExpression(this, r) {
              override def _evaluate[T: Ordering](l: T, r: T) = compare(l, r) < 0
          }
          
          def <=[R: Reads](r: JpConstantExpression[R]) = new JpBinaryExpression(this, r) {
              override def _evaluate[T: Ordering](l: T, r: T) = compare(l, r) <= 0
          }
          
          def >[R: Reads](r: JpConstantExpression[R]) = new JpBinaryExpression(this, r) {
              override def _evaluate[T: Ordering](l: T, r: T) = compare(l, r) > 0
          }
          
          def >=[R: Reads](r: JpConstantExpression[R]) = new JpBinaryExpression(this, r) {
              override def _evaluate[T: Ordering](l: T, r: T) = compare(l, r) >= 0
          }

          // this works only when you don't have && or || after
          def ==[R: Reads](r: JpConstantExpression[R]) = new JpBinaryExpression(this, r) {
              override def _evaluate[T: Ordering](l: T, r: T) = compare(l, r) == 0
          }

          def ===[R: Reads](r: JpConstantExpression[R]) = new JpBinaryExpression(this, r) {
              override def _evaluate[T: Ordering](l: T, r: T) = compare(l, r) == 0
          }

          def <>[R: Reads](r: JpConstantExpression[R]) = new JpBinaryExpression(this, r) {
              override def _evaluate[T: Ordering](l: T, r: T) = compare(l, r) != 0
          }

          def selectDynamic(path: String) = apply(path)
          
          def applyDynamic(path: String)(index: Int) = apply(path).apply(index)
          
          def apply(path: String) =
              new JpPathExpression((paths.toSeq ++ Seq(Left(path))): _*)
          
          def apply(index: Int) = new JpPathExpression((paths.toSeq ++ Seq(Right(index))): _*)
          
          override def evaluate(filter: JpPathFilter, doc: DynaJson)(implicit PathNaming: PathNaming) = {
              doc.lookupResult match {
                  case JsDefined(jsValue) =>
                      val values = jsValue match {
                          case a: JsArray => a.value.zipWithIndex.view.map {v => (v._2, v._1)}.toSet
                          case m: JsObject => m.fieldSet.asInstanceOf[Set[(String, JsValue)]]
                          case v => Set((null, v))
                      }
                      values.map { row => 
                          var node = new DynaJson(new JsDefined(row._2))
                          paths.foreach{ path =>
                              node = path match {
                                  case Left(s) => node.apply(s)
                                  case Right(i) => node.apply(i)
                              }
                          }
                          (row, node.lookupResult)
                      }.filter { result =>
                          result._2.isInstanceOf[JsDefined]
                      }.map{ result =>
                          JpResult(result._1._1, result._1._2, result._2.get)
                      }.toSeq
                  case _: JsUndefined => Seq.empty
              }
          }
      }
      
      trait JpPathFilter {
          def find(path: DynaJson)(implicit PathNaming: PathNaming): DynaJson
      }
      
      protected class JpListFilter(e: JpExpression) extends JpPathFilter {
          def find(path: DynaJson)(implicit PathNaming: PathNaming): DynaJson = {
            val v = e.evaluate(this, path).map{ e => e.doc }.toSeq
            new DynaJson(JsArray(v))
          }
      }

      implicit def DynaJsonToJsLookupResult(doc: DynaJson) = doc.lookupResult

      implicit def intToExpression(i: Int) = new JpConstantExpression(i)

      implicit def doubleToExpression(d: Double) = new JpConstantExpression(d)

      implicit def stringToExpression(s: String) = new JpConstantExpression(s)

      implicit def dateTimeToExpression(dt: DateTime) = new JpConstantExpression(dt)

      implicit class JpJsValueWrapper(jsValue: JsValue) {
          def $(implicit PathNaming: PathNaming) = new DynaJson(jsValue)
      }
    
      // option filter: returns one or no row
      def ?(e: JpExpression) = new JpPathFilter {
          def find(path: DynaJson)(implicit PathNaming: PathNaming): DynaJson = {
            val v = e.evaluate(this, path).headOption match {
                case Some(e) => JsDefined(e.doc.asInstanceOf[JsValue])
                case None => new JsUndefined("")
            }
            new DynaJson(v)
          }
      }
      
      // wild-card filter: returns an array
      def *(e: JpExpression) = new JpListFilter(e)

      def * = -1
      
      // start from the current node in a filter expression
      def % = new Dynamic {
          def selectDynamic(key: String) = new JpPathExpression(Left(key))
          def applyDynamic(key: String) = new JpPathExpression(Left(key))
      }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy