package play.api.libs.json
import play.api.data.validation.ValidationError
sealed trait PathNode {
def apply(json: JsValue): List[JsValue]
def toJsonString: String
private[json] def splitChildren(json: JsValue): List[Either[(PathNode, JsValue), (PathNode, JsValue)]]
def set(json: JsValue, transform: JsValue => JsValue): JsValue
private[json] def toJsonField(value: JsValue): JsValue = value
}
case class RecursiveSearch(key: String) extends PathNode {
def apply(json: JsValue): List[JsValue] = json match {
case obj: JsObject => (json \\ key).toList
case arr: JsArray => (json \\ key).toList
case _ => Nil
}
override def toString = "//" + key
def toJsonString = "*" + key
def set(json: JsValue, transform: JsValue => JsValue): JsValue = json match {
case obj: JsObject =>
var found = false
val o = JsObject(obj.fields.map {
case (k, v) =>
if (k == this.key) {
found = true
k -> transform(v)
} else k -> set(v, transform)
})
o
case _ => json
}
private[json] def splitChildren(json: JsValue) = json match {
case obj: JsObject => obj.fields.toList.map {
case (k, v) =>
if (k == this.key) Right(this -> v)
else Left(KeyPathNode(k) -> v)
}
case arr: JsArray =>
arr.value.toList.zipWithIndex.map { case (js, j) => Left(IdxPathNode(j) -> js) }
case _ => List()
}
}
case class KeyPathNode(key: String) extends PathNode {
def apply(json: JsValue): List[JsValue] = json match {
case obj: JsObject => List(json \ key).flatMap(_.toOption)
case _ => List()
}
override def toString = "/" + key
def toJsonString = "." + key
def set(json: JsValue, transform: JsValue => JsValue): JsValue = json match {
case obj: JsObject =>
var found = false
val o = JsObject(obj.fields.map {
case (k, v) =>
if (k == this.key) {
found = true
k -> transform(v)
} else k -> v
})
if (!found) o ++ Json.obj(this.key -> transform(Json.obj()))
else o
case _ => transform(json)
}
private[json] def splitChildren(json: JsValue) = json match {
case obj: JsObject => obj.fields.toList.map {
case (k, v) =>
if (k == this.key) Right(this -> v)
else Left(KeyPathNode(k) -> v)
}
case _ => List()
}
private[json] override def toJsonField(value: JsValue) = Json.obj(key -> value)
}
case class IdxPathNode(idx: Int) extends PathNode {
def apply(json: JsValue): List[JsValue] = json match {
case arr: JsArray => List(arr(idx)).flatMap(_.toOption)
case _ => List()
}
override def toString = "(%d)".format(idx)
def toJsonString = "[%d]".format(idx)
def set(json: JsValue, transform: JsValue => JsValue): JsValue = json match {
case arr: JsArray => JsArray(arr.value.zipWithIndex.map { case (js, j) => if (j == idx) transform(js) else js })
case _ => transform(json)
}
private[json] def splitChildren(json: JsValue) = json match {
case arr: JsArray => arr.value.toList.zipWithIndex.map {
case (js, j) =>
if (j == idx) Right(this -> js)
else Left(IdxPathNode(j) -> js)
}
case _ => List()
}
private[json] override def toJsonField(value: JsValue) = value
}
object JsPath extends JsPath(List.empty) {
def createObj(pathValues: (JsPath, JsValue)*) = {
def buildSubPath(path: JsPath, value: JsValue) = {
def step(path: List[PathNode], value: JsValue): JsObject = {
path match {
case List() => value match {
case obj: JsObject => obj
case _ => throw new RuntimeException("when empty JsPath, expecting JsObject")
}
case List(p) => p match {
case KeyPathNode(key) => Json.obj(key -> value)
case _ => throw new RuntimeException("expected KeyPathNode")
}
case head :: tail => head match {
case KeyPathNode(key) => Json.obj(key -> step(tail, value))
case _ => throw new RuntimeException("expected KeyPathNode")
}
}
}
step(path.path, value)
}
pathValues.foldLeft(Json.obj()) { (obj, pv) =>
val (path, value) = (pv._1, pv._2)
val subobj = buildSubPath(path, value)
obj.deepMerge(subobj)
}
}
}
case class JsPath(path: List[PathNode] = List()) {
def \(child: String) = JsPath(path :+ KeyPathNode(child))
def \(child: Symbol) = JsPath(path :+ KeyPathNode(child.name))
def \\(child: String) = JsPath(path :+ RecursiveSearch(child))
def \\(child: Symbol) = JsPath(path :+ RecursiveSearch(child.name))
def apply(idx: Int): JsPath = JsPath(path :+ IdxPathNode(idx))
def apply(json: JsValue): List[JsValue] = path.foldLeft(List(json))((s, p) => s.flatMap(p.apply))
def asSingleJsResult(json: JsValue): JsResult[JsValue] = this(json) match {
case Nil => JsError(Seq(this -> Seq(ValidationError("error.path.missing"))))
case List(js) => JsSuccess(js)
case _ :: _ => JsError(Seq(this -> Seq(ValidationError("error.path.result.multiple"))))
}
def asSingleJson(json: JsValue): JsLookupResult = this(json) match {
case Nil => JsUndefined("error.path.missing")
case List(js) => JsDefined(js)
case _ :: _ => JsUndefined("error.path.result.multiple")
}
def applyTillLast(json: JsValue): Either[JsError, JsResult[JsValue]] = {
def step(path: List[PathNode], json: JsValue): Either[JsError, JsResult[JsValue]] = path match {
case Nil => Left(JsError(Seq(this -> Seq(ValidationError("error.path.empty")))))
case List(node) => node(json) match {
case Nil => Right(JsError(Seq(this -> Seq(ValidationError("error.path.missing")))))
case List(js) => Right(JsSuccess(js))
case _ :: _ => Right(JsError(Seq(this -> Seq(ValidationError("error.path.result.multiple")))))
}
case head :: tail => head(json) match {
case Nil => Left(JsError(Seq(this -> Seq(ValidationError("error.path.missing")))))
case List(js) => step(tail, js)
case _ :: _ => Left(JsError(Seq(this -> Seq(ValidationError("error.path.result.multiple")))))
}
}
step(path, json)
}
override def toString = path.mkString
def toJsonString = path.foldLeft("obj")((acc, p) => acc + p.toJsonString)
def compose(other: JsPath) = JsPath(path ++ other.path)
def ++(other: JsPath) = this compose other
def prune(js: JsValue) = {
def stepNode(json: JsObject, node: PathNode): JsResult[JsObject] = {
node match {
case KeyPathNode(key) => JsSuccess(json - key)
case _ => JsError(JsPath(), ValidationError("error.expected.keypathnode"))
}
}
def filterPathNode(json: JsObject, node: PathNode, value: JsValue): JsResult[JsObject] = {
node match {
case KeyPathNode(key) => JsSuccess(JsObject(json.fields.filterNot(_._1 == key)) ++ Json.obj(key -> value))
case _ => JsError(JsPath(), ValidationError("error.expected.keypathnode"))
}
}
def step(json: JsObject, lpath: JsPath): JsResult[JsObject] = {
lpath.path match {
case Nil => JsSuccess(json)
case List(p) => stepNode(json, p).repath(lpath)
case head :: tail => head(json) match {
case Nil => JsError(lpath, ValidationError("error.path.missing"))
case List(js) =>
js match {
case o: JsObject =>
step(o, JsPath(tail)).repath(lpath).flatMap(value =>
filterPathNode(json, head, value)
)
case _ => JsError(lpath, ValidationError("error.expected.jsobject"))
}
case h :: t => JsError(lpath, ValidationError("error.path.result.multiple"))
}
}
}
js match {
case o: JsObject => step(o, this) match {
case s: JsSuccess[JsObject] => s.copy(path = this)
case e => e
}
case _ =>
JsError(this, ValidationError("error.expected.jsobject"))
}
}
def read[T](implicit r: Reads[T]): Reads[T] = Reads.at[T](this)(r)
def readNullable[T](implicit r: Reads[T]): Reads[Option[T]] = Reads.nullable[T](this)(r)
def lazyRead[T](r: => Reads[T]): Reads[T] = Reads(js => Reads.at[T](this)(r).reads(js))
def lazyReadNullable[T](r: => Reads[T]): Reads[Option[T]] = Reads(js => Reads.nullable[T](this)(r).reads(js))
def read[T](t: T) = Reads.pure(t)
def write[T](implicit w: Writes[T]): OWrites[T] = Writes.at[T](this)(w)
def writeNullable[T](implicit w: Writes[T]): OWrites[Option[T]] = Writes.nullable[T](this)(w)
def lazyWrite[T](w: => Writes[T]): OWrites[T] = OWrites((t: T) => Writes.at[T](this)(w).writes(t))
def lazyWriteNullable[T](w: => Writes[T]): OWrites[Option[T]] = OWrites((t: Option[T]) => Writes.nullable[T](this)(w).writes(t))
def write[T](t: T)(implicit w: Writes[T]): OWrites[JsValue] = Writes.pure(this, t)
def format[T](implicit f: Format[T]): OFormat[T] = Format.at[T](this)(f)
def format[T](r: Reads[T])(implicit w: Writes[T]): OFormat[T] = Format.at[T](this)(Format(r, w))
def format[T](w: Writes[T])(implicit r: Reads[T]): OFormat[T] = Format.at[T](this)(Format(r, w))
def rw[T](implicit r: Reads[T], w: Writes[T]): OFormat[T] = Format.at[T](this)(Format(r, w))
def formatNullable[T](implicit f: Format[T]): OFormat[Option[T]] = Format.nullable[T](this)(f)
def lazyFormat[T](f: => Format[T]): OFormat[T] = OFormat[T](lazyRead(f), lazyWrite(f))
def lazyFormatNullable[T](f: => Format[T]): OFormat[Option[T]] = OFormat[Option[T]](lazyReadNullable(f), lazyWriteNullable(f))
def lazyFormat[T](r: => Reads[T], w: => Writes[T]): OFormat[T] = OFormat[T](lazyRead(r), lazyWrite(w))
def lazyFormatNullable[T](r: => Reads[T], w: => Writes[T]): OFormat[Option[T]] = OFormat[Option[T]](lazyReadNullable(r), lazyWriteNullable(w))
private val self = this
object json {
def pick[A <: JsValue](implicit r: Reads[A]): Reads[A] = Reads.jsPick(self)
def pick: Reads[JsValue] = pick[JsValue]
def pickBranch[A <: JsValue](reads: Reads[A]): Reads[JsObject] = Reads.jsPickBranch[A](self)(reads)
def pickBranch: Reads[JsObject] = Reads.jsPickBranch[JsValue](self)
def put(a: => JsValue): Reads[JsObject] = Reads.jsPut(self, a)
def copyFrom[A <: JsValue](reads: Reads[A]): Reads[JsObject] = Reads.jsCopyTo(self)(reads)
def update[A <: JsValue](reads: Reads[A]): Reads[JsObject] = Reads.jsUpdate(self)(reads)
def prune: Reads[JsObject] = Reads.jsPrune(self)
}
}