summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Rudolph <johannes_rudolph@gmx.de>2012-05-30 14:03:28 +0200
committerJohannes Rudolph <johannes_rudolph@gmx.de>2012-05-30 14:03:28 +0200
commit9491c92a66d496ca336f300cee26ecad81b746ea (patch)
treeacfb8a5e13cd162f93ab2e4c7517b3f1c878f21f
parentd4e77ce2b3ffe1c9929e955cfb45ffb5e2f79e28 (diff)
downloadspray-json-9491c92a66d496ca336f300cee26ecad81b746ea.tar.gz
spray-json-9491c92a66d496ca336f300cee26ecad81b746ea.tar.bz2
spray-json-9491c92a66d496ca336f300cee26ecad81b746ea.zip
implement `elements` + `filter` and most of the combinators
-rw-r--r--src/main/scala/cc/spray/json/JsonLenses.scala196
-rw-r--r--src/test/scala/cc/spray/json/JsonLensesSpec.scala25
2 files changed, 166 insertions, 55 deletions
diff --git a/src/main/scala/cc/spray/json/JsonLenses.scala b/src/main/scala/cc/spray/json/JsonLenses.scala
index ae197b6..db86376 100644
--- a/src/main/scala/cc/spray/json/JsonLenses.scala
+++ b/src/main/scala/cc/spray/json/JsonLenses.scala
@@ -1,5 +1,7 @@
package cc.spray.json
+import annotation.tailrec
+
object JsonLenses {
type JsPred = JsValue => Boolean
type Id[T] = T
@@ -77,7 +79,7 @@ object JsonLenses {
def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue
}
trait Projection[M[_]] extends Updateable {
- type ThenScalar
+ type AndScalar
def retr: JsValue => Validated[M[JsValue]]
def mapValue[T](value: M[JsValue])(f: JsValue => Validated[T]): Validated[M[T]]
@@ -85,48 +87,42 @@ object JsonLenses {
def get[T: MonadicReader]: JsValue => M[T]
def ![U](op: Operation): Update
- //def is(f: M[T] => Boolean): JsPred
def is[U: MonadicReader](f: U => Boolean): JsPred
- def andThen(next: ScalarProjection): ThenScalar
+ def andThen(next: ScalarProjection): AndScalar
+ def andThen(next: SeqProjection): SeqProjection
def /(fieldName: String) = this andThen fieldName
def apply(idx: Int) = element(idx)
def element(idx: Int) = this andThen JsonLenses.element(idx)
-
- //def /[M2[_], R[_]](next: Projection[M2])(implicit conv: Conv[M, M2, R]): Projection[R]
}
- /*
- trait Conv[M[_], M2[_], R[_]] {
- //def flatMap[T](first: M[T], second: M2[T])(op: (T, T) => T): R[T]
- }
- object Conv {
- implicit def joinScalar[M2[_]]: Conv[Id, M2, M2] = new Conv[Id, M2, M2] {
- def flatMap[T](first: T, second: M2[T])(op: (T, T) => T): M2[T] = ???.asInstanceOf[M2[T]]
-
- }
- implicit def joinSeq[M2[_]]: Conv[Seq, M2, Seq] = ???
- implicit def joinOptId: Conv[Option, Id, Option] = ???
- implicit def joinOptOpt: Conv[Option, Option, Option] = ???
- implicit def joinOptSeq: Conv[Option, Seq, Seq] = ???
- }*/
trait ScalarProjection extends Projection[Id] {
- type ThenScalar = ScalarProjection
+ type AndScalar = ScalarProjection
- def andThen(next: ScalarProjection): ScalarProjection
def andThen(next: OptProjection): OptProjection
}
trait OptProjection extends Projection[Option] {
- type ThenScalar = OptProjection
+ type AndScalar = OptProjection
- def andThen(next: ScalarProjection): OptProjection
def andThen(next: OptProjection): OptProjection
}
- type SeqProjection = Projection[Seq]
+ trait SeqProjection extends Projection[Seq] {
+ type AndScalar = SeqProjection
+
+ def andThen(next: OptProjection): SeqProjection
+ }
+
+ trait Monad[M[_]] {
+ def map[T, U](els: M[JsValue])(f: JsValue => U): M[U]
+ def flatMap[T, U](els: M[JsValue])(f: JsValue => Seq[U]): Seq[U]
+ def allRight[T](v: M[Validated[T]]): Validated[M[T]]
+ }
trait ProjectionImpl[M[_]] extends Projection[M] { outer =>
+ def mon: Monad[M]
+
def getSecure[T: MonadicReader]: JsValue => Validated[M[T]] =
p => retr(p).flatMap(mapValue(_)(_.as[T]))
@@ -142,22 +138,52 @@ object JsonLenses {
def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
outer.updated(_.flatMap(next.updated(f)))(parent)
}
+
+ def mapValue[T](value: M[JsValue])(f: JsValue => Validated[T]): Validated[M[T]] =
+ mon.allRight(mon.map(value)(f))
+
+ def is[U: MonadicReader](f: (U) => Boolean): JsonLenses.JsPred = ???
+
+ def build(next: Updateable)(f: JsValue => Validated[M[JsValue]]): AndScalar
+
+ def andThen(next: ScalarProjection): AndScalar =
+ build(next) { parent =>
+ for {
+ outerV <- outer.retr(parent)
+ innerV <- mon.allRight(mon.map(outerV)(next.retr))
+ } yield innerV
+ }
+
+ def andThen(next: SeqProjection): SeqProjection = new Joined(next) with SeqProjectionImpl {
+ def retr: JsValue => Validated[Seq[JsValue]] = parent =>
+ for {
+ outerV <- outer.retr(parent)
+ innerV <- allRightF(outer.mon.flatMap(outerV)(x => swap(next.retr(x))))
+ } yield innerV
+ }
+
+ def swap[T](x: Validated[Seq[T]]): Seq[Validated[T]] = x match {
+ case Right(x) => x.map(Right(_))
+ case Left(e) => List(Left(e))
+ }
}
trait ScalarProjectionImpl extends ScalarProjection with ProjectionImpl[Id] { outer =>
- def mapValue[T](value: JsValue)(f: JsValue => Validated[T]): Validated[T] =
- f(value)
-
- def is[U: MonadicReader](f: U => Boolean): JsPred =
+ override def is[U: MonadicReader](f: U => Boolean): JsPred =
value => getSecure[U] apply value exists f
- def andThen(next: ScalarProjection): ScalarProjection =
+ def mon: Monad[Id] = new Monad[Id] {
+ def map[T, U](els: JsValue)(f: JsValue => U): U = f(els)
+
+ def flatMap[T, U](els: JsValue)(f: JsValue => Seq[U]): Seq[U] =
+ f(els)
+
+ def allRight[T](v: Validated[T]): Validated[T] = v
+ }
+
+ def build(next: Updateable)(f: JsValue => Validated[JsValue]): ScalarProjection =
new Joined(next) with ScalarProjectionImpl {
- def retr: JsValue => SafeJsValue = parent =>
- for {
- outerV <- outer.retr(parent)
- innerV <- next.retr(outerV)
- } yield innerV
+ def retr: JsValue => Validated[JsValue] = f
}
def andThen(next: OptProjection): OptProjection = new Joined(next) with OptProjectionImpl {
@@ -170,33 +196,34 @@ object JsonLenses {
}
trait OptProjectionImpl extends OptProjection with ProjectionImpl[Option] { outer =>
- def mapValue[T](value: Option[JsValue])(f: JsValue => Validated[T]): Validated[Option[T]] =
- swap(value.map(f))
+ def mon: Monad[Option] = new Monad[Option] {
+ def map[T, U](els: Option[JsValue])(f: JsValue => U): Option[U] =
+ els.map(f)
+
+ def flatMap[T, U](els: Option[JsValue])(f: JsValue => Seq[U]): Seq[U] =
+ els.toSeq.flatMap(f)
+
+ def allRight[T](v: Option[Validated[T]]): Validated[Option[T]] =
+ v match {
+ case None => Right(None)
+ case Some(Right(x)) => Right(Some(x))
+ case Some(Left(e)) => Left(e)
+ }
+ }
- def is[U: MonadicReader](f: U => Boolean): JsPred = ???
+ def build(next: Updateable)(f: JsValue => Validated[Option[JsValue]]): OptProjection =
+ new Joined(next) with OptProjectionImpl {
+ def retr: JsValue => Validated[Option[JsValue]] = f
+ }
- def andThen(next: ScalarProjection): OptProjection = new Joined(next) with OptProjectionImpl {
- def retr: JsValue => Validated[Option[JsValue]] = parent =>
- for {
- outerV <- outer.retr(parent)
- innerV <- swap(outerV.map(next.retr))
- } yield innerV
- }
def andThen(next: OptProjection): OptProjection = new Joined(next) with OptProjectionImpl {
def retr: JsValue => Validated[Option[JsValue]] = parent =>
for {
outerV <- outer.retr(parent)
- innerV <- swap(outerV.flatMap(x => swap(next.retr(x))))
+ innerV <- mon.allRight(outerV.flatMap(x => swap(next.retr(x))))
} yield innerV
}
- def swap[T](v: Option[Validated[T]]): Validated[Option[T]] =
- v match {
- case None => Right(None)
- case Some(Right(x)) => Right(Some(x))
- case Some(Left(e)) => Left(e)
- }
-
def swap[T](v: Validated[Option[T]]): Option[Validated[T]] =
v match {
case Right(None) => None
@@ -205,10 +232,31 @@ object JsonLenses {
}
}
+ trait SeqProjectionImpl extends SeqProjection with ProjectionImpl[Seq] { outer =>
+ def mon: Monad[Seq] = new Monad[Seq] {
+ def map[T, U](els: Seq[JsValue])(f: JsValue => U): Seq[U] =
+ els.map(f)
+
+ override def flatMap[T, U](els: Seq[JsValue])(f: JsValue => Seq[U]): Seq[U] =
+ els.flatMap(f)
+
+ def allRight[T](v: Seq[Validated[T]]): Validated[Seq[T]] =
+ allRightF(v)
+ }
+
+
+ def build(next: Updateable)(f: JsValue => Validated[Seq[JsValue]]): SeqProjection =
+ new Joined(next) with SeqProjectionImpl {
+ def retr: JsValue => Validated[Seq[JsValue]] = f
+ }
+
+ def andThen(next: OptProjection): SeqProjection = ???
+ }
+
def field(name: String): ScalarProjection = new ScalarProjectionImpl {
def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
for {
- res <- f(getField(parent))
+ res <- f(getField(parent))
}
yield JsObject(fields = parent.asJsObject.fields + (name -> res))
@@ -258,8 +306,32 @@ object JsonLenses {
def retr: JsValue => SafeJsValue = x => Right(x)
}
- def elements: SeqProjection = ???
+ def elements: SeqProjection = new SeqProjectionImpl {
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
+ case JsArray(elements) =>
+ mapAllRight(elements)(v => f(Right(v))) map (JsArray(_: _*))
+ case e@_ => unexpected("Not a json array: "+e)
+ }
+ def retr: JsValue => Validated[Seq[JsValue]] = {
+ case JsArray(elements) => Right(elements)
+ case e@_ => unexpected("Not a json array: "+e)
+ }
+ }
+ def filter(pred: JsPred): SeqProjection = new SeqProjectionImpl {
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
+ //case JsArray(elements) =>
+
+
+ case e@_ => unexpected("Not a json array: "+e)
+ }
+
+ def retr: JsValue => Validated[Seq[JsValue]] = {
+ case JsArray(elements) =>
+ Right(elements.filter(pred))
+ case e@_ => unexpected("Not a json array: "+e)
+ }
+ }
def find(pred: JsPred): OptProjection = new OptProjectionImpl {
def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
case JsArray(elements) =>
@@ -296,7 +368,6 @@ object JsonLenses {
value.as[T] map (v => jsonWriter[T].write(f(v)))
}
- def filter(pred: JsPred): SeqProjection = ???
def append(update: Update): Operation = ???
def update(update: Update): Operation = ???
@@ -306,4 +377,19 @@ object JsonLenses {
def unexpected(message: String) = Left(new RuntimeException(message))
def outOfBounds(message: String) = Left(new IndexOutOfBoundsException(message))
+
+ def mapAllRight[T, U](l: List[T])(f: T => Validated[U]): Validated[Seq[U]] = {
+ def inner(l: List[T]): Validated[List[U]] = l match {
+ case head :: tail =>
+ for {
+ headM <- f(head)
+ tailM <- inner(tail)
+ } yield headM :: tailM
+ case Nil =>
+ Right(Nil)
+ }
+ inner(l)
+ }
+ def allRightF[T](l: Seq[Validated[T]]): Validated[Seq[T]] =
+ mapAllRight(l.toList)(identity)
} \ No newline at end of file
diff --git a/src/test/scala/cc/spray/json/JsonLensesSpec.scala b/src/test/scala/cc/spray/json/JsonLensesSpec.scala
index d6407c1..766084f 100644
--- a/src/test/scala/cc/spray/json/JsonLensesSpec.scala
+++ b/src/test/scala/cc/spray/json/JsonLensesSpec.scala
@@ -60,6 +60,31 @@ class JsonLensesSpec extends Specification with SpecHelpers {
}
}
}
+ "all elements of an array" in {
+ "simple" in {
+ """[18, 23, 2, 5, 8, 3]""" extract elements.get[Int] must be_==(Seq(18, 23, 2, 5, 8, 3))
+ }
+ "which is a scalar element" in {
+ """{"a": [1, 2, 3, 4]}""" extract ("a" andThen elements).get[Int] must be_==(Seq(1, 2, 3, 4))
+ }
+ "field of an array element" in {
+ """[{"a": 1}, {"a": 2}]""" extract (elements andThen "a").get[Int] must be_==(Seq(1, 2))
+ }
+ "nested" in {
+ """[[1, 2], [3, 4]]""" extract (elements andThen elements).get[Int] must be_==(Seq(1, 2, 3, 4))
+ }
+ "if outer is no array" in {
+ """{"a": 5}""" extract (elements / "a").get[Int] must throwAn[Exception]("""Not a json array: {"a":5}""")
+ """{"a": 5}""" extract (elements andThen elements).get[Int] must throwAn[Exception]("""Not a json array: {"a":5}""")
+ }
+ "if inner is no array" in {
+ """[{}, {}]""" extract (elements andThen elements).get[Int] must throwAn[Exception]("""Not a json array: {}""")
+ """{"a": 5}""" extract ("a" andThen elements).get[Int] must throwAn[Exception]("""Not a json array: 5""")
+ }
+ }
+ /*"filtered elements of an array" in {
+
+ }*/
}
"modify" in {