summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Rudolph <johannes_rudolph@gmx.de>2012-05-31 16:18:18 +0200
committerJohannes Rudolph <johannes_rudolph@gmx.de>2012-05-31 16:18:18 +0200
commit80009f36f965e5237a0b2e3789da327658fec871 (patch)
tree997ee44b1fac2d1e576977d8e81c6c9ca031148b
parentce42a26d51f40c094abdbdf7c3b48def74714131 (diff)
downloadspray-json-80009f36f965e5237a0b2e3789da327658fec871.tar.gz
spray-json-80009f36f965e5237a0b2e3789da327658fec871.tar.bz2
spray-json-80009f36f965e5237a0b2e3789da327658fec871.zip
big file split up for better readability
-rw-r--r--src/main/scala/cc/spray/json/lenses/Join.scala42
-rw-r--r--src/main/scala/cc/spray/json/lenses/JsonLenses.scala451
-rw-r--r--src/main/scala/cc/spray/json/lenses/JsonPathIntegration.scala44
-rw-r--r--src/main/scala/cc/spray/json/lenses/Operations.scala29
-rw-r--r--src/main/scala/cc/spray/json/lenses/Ops.scala63
-rw-r--r--src/main/scala/cc/spray/json/lenses/OptionLenses.scala26
-rw-r--r--src/main/scala/cc/spray/json/lenses/Projection.scala56
-rw-r--r--src/main/scala/cc/spray/json/lenses/ReadLens.scala35
-rw-r--r--src/main/scala/cc/spray/json/lenses/ScalarLenses.scala61
-rw-r--r--src/main/scala/cc/spray/json/lenses/SeqLenses.scala44
-rw-r--r--src/main/scala/cc/spray/json/lenses/UpdateLens.scala32
-rw-r--r--src/main/scala/cc/spray/json/lenses/package.scala79
12 files changed, 517 insertions, 445 deletions
diff --git a/src/main/scala/cc/spray/json/lenses/Join.scala b/src/main/scala/cc/spray/json/lenses/Join.scala
new file mode 100644
index 0000000..03faeb6
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/Join.scala
@@ -0,0 +1,42 @@
+package cc.spray.json
+package lenses
+
+/**
+ * This typeclass with its implicit instances decides how two containers should be joined.
+ *
+ * Supported containers are
+ *
+ * * `Id` for scalar values
+ * * `Option` for optional values
+ * * `Seq` for a vector of values
+ *
+ * Those container types form an ordering from most specific to most abstract:
+ *
+ * * `Id` contains always one value
+ * * `Option` contains always zero or one value
+ * * `Seq` can contain any number of values
+ *
+ * The rule to determine what the result type of joining two container types is that the result
+ * is as generic as the more generic of both of the input types.
+ *
+ * The implicit definitions in the companion object of join form evidence for this ordering.
+ */
+trait Join[M1[_], M2[_], R[_]] {
+ def get(outer: Ops[M1], inner: Ops[M2]): Ops[R]
+}
+
+object Join {
+ def apply[M1[_], M2[_], R[_]](f: ((Ops[M1], Ops[M2])) => Ops[R]): Join[M1, M2, R] = new Join[M1, M2, R] {
+ def get(outer: Ops[M1], inner: Ops[M2]): Ops[R] = f(outer, inner)
+ }
+
+ implicit def joinWithSeq[M2[_]]: Join[Seq, M2, Seq] = Join(_._1)
+
+ implicit def joinWithScalar[M2[_]]: Join[Id, M2, M2] = Join(_._2)
+
+ implicit def joinWithOptionWithId: Join[Option, Id, Option] = Join(_._1)
+
+ implicit def joinOptionWithOption: Join[Option, Option, Option] = Join(_._1)
+
+ implicit def joinOptionWithSeq: Join[Option, Seq, Seq] = Join(_._2)
+} \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/JsonLenses.scala b/src/main/scala/cc/spray/json/lenses/JsonLenses.scala
index 1468565..494a581 100644
--- a/src/main/scala/cc/spray/json/lenses/JsonLenses.scala
+++ b/src/main/scala/cc/spray/json/lenses/JsonLenses.scala
@@ -1,453 +1,14 @@
package cc.spray.json
package lenses
-object JsonLenses {
- type JsPred = JsValue => Boolean
- type Id[T] = T
- type Validated[T] = Either[Exception, T]
- type SafeJsValue = Validated[JsValue]
-
- type Operation = SafeJsValue => SafeJsValue
-
- type ScalarProjection = Projection[Id]
- type OptProjection = Projection[Option]
- type SeqProjection = Projection[Seq]
-
- implicit def rightBiasEither[A, B](e: Either[A, B]): Either.RightProjection[A, B] = e.right
-
- case class GetOrThrow[B](e: Either[Throwable, B]) {
- def getOrThrow: B = e match {
- case Right(b) => b
- case Left(e) => throw e
- }
- }
-
- implicit def orThrow[B](e: Either[Throwable, B]): GetOrThrow[B] = GetOrThrow(e)
-
- trait MonadicReader[T] {
- def read(js: JsValue): Validated[T]
- }
-
- object MonadicReader {
- implicit def safeMonadicReader[T: JsonReader]: MonadicReader[T] = new MonadicReader[T] {
- def read(js: JsValue): Validated[T] =
- safe(js.convertTo[T])
- }
- }
-
- def safe[T](body: => T): Validated[T] =
- try {
- Right(body)
- } catch {
- case e: Exception => Left(e)
- }
-
- case class ValidateOption[T](option: Option[T]) {
- def getOrError(message: => String): Validated[T] = option match {
- case Some(t) => Right(t)
- case None => unexpected(message)
- }
- }
-
- implicit def validateOption[T](o: Option[T]): ValidateOption[T] = ValidateOption(o)
-
- trait Update extends (JsValue => JsValue) {
- outer =>
- def apply(value: JsValue): JsValue
-
- def &&(next: Update): Update = new Update {
- def apply(value: JsValue): JsValue = next(outer(value))
- }
- }
+object JsonLenses extends
+ ScalarLenses with
+ OptionLenses with
+ SeqLenses with
+ Operations with
+ JsonPathIntegration {
implicit def strToField(name: String): ScalarProjection = field(name)
-
implicit def symbolToField(sym: Symbol): ScalarProjection = field(sym.name)
- case class RichJsValue(value: JsValue) {
- def update(updater: Update): JsValue = updater(value)
-
- def update[T: JsonWriter, M[_]](lens: UpdateLens, pValue: T): JsValue = lens ! set(pValue) apply value
-
- // This can't be simplified because we don't want the type constructor
- // of projection to appear in the type paramater list.
- def extract[T: MonadicReader](p: Projection[Id]): T =
- p.get[T](value)
-
- def extract[T: MonadicReader](p: Projection[Option]): Option[T] =
- p.get[T](value)
-
- def extract[T: MonadicReader](p: Projection[Seq]): Seq[T] =
- p.get[T](value)
-
- def as[T: MonadicReader]: Validated[T] =
- implicitly[MonadicReader[T]].read(value)
- }
-
- implicit def updatable(value: JsValue): RichJsValue = RichJsValue(value)
-
- /**
- * The UpdateLens is the central interface for updating a child element somewhere
- * deep down a hierarchy of a JsValue.
- */
- trait UpdateLens {
- /**
- * Applies function `f` on the child of the `parent` denoted by this UpdateLens
- * and returns a `Right` of the parent with the child element updated.
- *
- * The value passed to `f` may be `Left(e)` if the child could not be found
- * in which case particular operations may still succeed. Function `f` may return
- * `Left(error)` in case the operation fails.
- *
- * `updated` returns `Left(error)` if the update operation or any of any intermediate
- * projections fail.
- */
- def updated(f: Operation)(parent: JsValue): SafeJsValue
-
- def !(op: Operation): Update
- }
-
- /**
- * The read lens can extract child values out of a JsValue hierarchy. A read lens
- * is parameterized with a type constructor. This allows to extracts not only scalar
- * values but also sequences or optional values.
- * @tparam M
- */
- trait ReadLens[M[_]] {
- /**
- * Given a parent JsValue, tries to extract the child value.
- * @return `Right(value)` if the projection succeeds. `Left(error)` if the projection
- * fails.
- */
- def retr: JsValue => Validated[M[JsValue]]
-
- /**
- * Given a parent JsValue extracts and tries to convert the JsValue into
- * a value of type `T`
- */
- def tryGet[T: MonadicReader](value: JsValue): Validated[M[T]]
-
- /**
- * Given a parent JsValue extracts and converts a JsValue into a value of
- * type `T` or throws an exception.
- */
- def get[T: MonadicReader](value: JsValue): M[T]
-
- /**
- * Lifts a predicate for a converted value for this lens up to the
- * parent level.
- */
- def is[U: MonadicReader](f: U => Boolean): JsPred
- }
-
- /**
- * A projection combines read and update functions of UpdateLens and ReadLens into
- * combinable chunks.
- * @tparam M
- */
- trait Projection[M[_]] extends UpdateLens with ReadLens[M] {
- def /[M2[_], R[_]](next: Projection[M2])(implicit ev: Join[M2, M, R]): Projection[R]
-
- def toSeq: Projection[Seq]
-
- def ops: Ops[M]
- }
-
- trait Join[M1[_], M2[_], R[_]] {
- def get(outer: Ops[M1], inner: Ops[M2]): Ops[R]
- }
-
- object Join {
- def apply[M1[_], M2[_], R[_]](f: ((Ops[M1], Ops[M2])) => Ops[R]): Join[M1, M2, R] = new Join[M1, M2, R] {
- def get(outer: Ops[M1], inner: Ops[M2]): Ops[R] = f(outer, inner)
- }
-
- implicit def joinWithSeq[M2[_]]: Join[Seq, M2, Seq] = Join(_._1)
-
- implicit def joinWithScalar[M2[_]]: Join[Id, M2, M2] = Join(_._2)
-
- implicit def joinWithOptionWithId: Join[Option, Id, Option] = Join(_._1)
-
- implicit def joinOptionWithOption: Join[Option, Option, Option] = Join(_._1)
-
- implicit def joinOptionWithSeq: Join[Option, Seq, Seq] = Join(_._2)
- }
-
- trait Ops[M[_]] {
- def flatMap[T, U](els: M[T])(f: T => Seq[U]): Seq[U]
-
- def allRight[T](v: Seq[Validated[T]]): Validated[M[T]]
-
- def toSeq[T](v: Validated[M[T]]): Seq[Validated[T]]
-
- def map[T, U](els: M[T])(f: T => U): Seq[U] =
- flatMap(els)(v => Seq(f(v)))
- }
-
- object Ops {
- implicit def idOps: Ops[Id] = new Ops[Id] {
- def flatMap[T, U](els: T)(f: T => Seq[U]): Seq[U] = f(els)
-
- def allRight[T](v: Seq[Validated[T]]): Validated[T] = v.head
-
- def toSeq[T](v: Validated[T]): Seq[Validated[T]] = Seq(v)
- }
-
- implicit def optionOps: Ops[Option] = new Ops[Option] {
- def flatMap[T, U](els: Option[T])(f: T => Seq[U]): Seq[U] =
- els.toSeq.flatMap(f)
-
- def allRight[T](v: Seq[Validated[T]]): Validated[Option[T]] =
- v match {
- case Nil => Right(None)
- case Seq(Right(x)) => Right(Some(x))
- case Seq(Left(e)) => Left(e)
- }
-
- def toSeq[T](v: Validated[Option[T]]): Seq[Validated[T]] = v match {
- case Right(Some(x)) => Seq(Right(x))
- case Right(None) => Nil
- case Left(e) => Seq(Left(e))
- }
- }
-
- implicit def seqOps: Ops[Seq] = new Ops[Seq] {
- def flatMap[T, U](els: Seq[T])(f: T => Seq[U]): Seq[U] =
- els.flatMap(f)
-
- def allRight[T](v: Seq[Validated[T]]): Validated[Seq[T]] = {
- def inner(l: List[Validated[T]]): Validated[List[T]] = l match {
- case head :: tail =>
- for {
- headM <- head
- tailM <- inner(tail)
- } yield headM :: tailM
- case Nil =>
- Right(Nil)
- }
- inner(v.toList)
- }
-
- def toSeq[T](x: Validated[Seq[T]]): Seq[Validated[T]] = x match {
- case Right(x) => x.map(Right(_))
- case Left(e) => List(Left(e))
- }
- }
- }
-
- trait ProjectionImpl[M[_]] extends Projection[M] {
- outer =>
- def tryGet[T: MonadicReader](p: JsValue): Validated[M[T]] =
- retr(p).flatMap(mapValue(_)(_.as[T]))
-
- def get[T: MonadicReader](p: JsValue): M[T] =
- tryGet[T](p).getOrThrow
-
- def !(op: Operation): Update = new Update {
- def apply(parent: JsValue): JsValue =
- updated(op)(parent).getOrThrow
- }
-
- def is[U: MonadicReader](f: U => Boolean): JsPred = value =>
- tryGet[U](value) exists (x => ops.map(x)(f).forall(identity))
-
- def /[M2[_], R[_]](next: Projection[M2])(implicit ev: Join[M2, M, R]): Projection[R] = new ProjectionImpl[R] {
- val ops: Ops[R] = ev.get(next.ops, outer.ops)
-
- def retr: JsValue => Validated[R[JsValue]] = parent =>
- for {
- outerV <- outer.retr(parent)
- innerV <- ops.allRight(outer.ops.flatMap(outerV)(x => next.ops.toSeq(next.retr(x))))
- } yield innerV
-
- def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
- outer.updated(_.flatMap(next.updated(f)))(parent)
- }
-
- def toSeq: Projection[Seq] = this / asSeq
-
- private[this] def mapValue[T](value: M[JsValue])(f: JsValue => Validated[T]): Validated[M[T]] =
- ops.allRight(ops.map(value)(f))
- }
-
- abstract class Proj[M[_]](implicit val ops: Ops[M]) extends ProjectionImpl[M]
-
- def field(name: String): ScalarProjection = new Proj[Id] {
- def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
- for {
- res <- f(getField(parent))
- }
- yield JsObject(fields = parent.asJsObject.fields + (name -> res))
-
- def retr: JsValue => SafeJsValue = v =>
- getField(v)
-
- def getField(v: JsValue): SafeJsValue = asObj(v) flatMap {
- o =>
- o.fields.get(name).getOrError("Expected field '%s' in '%s'" format(name, v))
- }
-
- def asObj(v: JsValue): Validated[JsObject] = v match {
- case o: JsObject =>
- Right(o)
- case e@_ =>
- unexpected("Not a json object: " + e)
- }
- }
-
- def element(idx: Int): ScalarProjection = new Proj[Id] {
- def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
- case JsArray(elements) =>
- if (idx < elements.size) {
- val (headEls, element :: tail) = elements.splitAt(idx)
- f(Right(element)) map (v => JsArray(headEls ::: v :: tail))
- } else
- unexpected("Too little elements in array: %s size: %d index: %d" format(parent, elements.size, idx))
- case e@_ =>
- unexpected("Not a json array: " + e)
- }
-
- def retr: JsValue => SafeJsValue = {
- case a@JsArray(elements) =>
- if (idx < elements.size)
- Right(elements(idx))
- else
- outOfBounds("Too little elements in array: %s size: %d index: %d" format(a, elements.size, idx))
- case e@_ => unexpected("Not a json array: " + e)
- }
- }
-
- /**
- * The identity projection which operates on the current element itself
- */
- val value: ScalarProjection = new Proj[Id] {
- def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
- f(Right(parent))
-
- def retr: JsValue => SafeJsValue = x => Right(x)
- }
- val asSeq: SeqProjection = new Proj[Seq] {
- def updated(f: Operation)(parent: JsValue): SafeJsValue =
- f(Right(parent))
-
- def retr: JsValue => JsonLenses.Validated[Seq[JsValue]] = x => Right(Seq(x))
- }
-
- val elements: SeqProjection = new Proj[Seq] {
- def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
- case JsArray(elements) =>
- ops.allRight(elements.map(x => f(Right(x)))).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)
- }
- }
-
- /**Alias for `elements`*/
- def * = elements
-
- def filter(pred: JsPred): SeqProjection = new Proj[Seq] {
- def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
- case JsArray(elements) =>
- ops.allRight(elements.map(x => if (pred(x)) f(Right(x)) else Right(x))).map(JsArray(_: _*))
-
- 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 Proj[Option] {
- def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
- case JsArray(elements) =>
- elements.span(x => !pred(x)) match {
- case (prefix, element :: suffix) =>
- f(Right(element)) map (v => JsArray(prefix ::: v :: suffix))
-
- // element not found, do nothing
- case _ =>
- Right(parent)
- }
- case e@_ => unexpected("Not a json array: " + e)
- }
-
- def retr: JsValue => Validated[Option[JsValue]] = {
- case JsArray(elements) => Right(elements.find(pred))
- case e@_ => unexpected("Not a json array: " + e)
- }
- }
-
- def set[T: JsonWriter](t: T): Operation = new Operation {
- def apply(value: SafeJsValue): SafeJsValue =
- // ignore existence of old value
- Right(t.toJson)
- }
-
- trait MapOperation extends Operation {
- def apply(value: JsValue): SafeJsValue
-
- def apply(value: SafeJsValue): SafeJsValue = value.flatMap(apply)
- }
-
- def updated[T: MonadicReader : JsonWriter](f: T => T): Operation = new MapOperation {
- def apply(value: JsValue): SafeJsValue =
- value.as[T] map (v => f(v).toJson)
- }
-
- def append(update: Update): Operation = ???
-
- def update(update: Update): Operation = ???
-
- def extract[M[_], T](value: Projection[M])(f: M[T] => Update): Operation = ???
-
- def ??? = sys.error("NYI")
-
- def unexpected(message: String) = Left(new RuntimeException(message))
-
- def outOfBounds(message: String) = Left(new IndexOutOfBoundsException(message))
-
- def fromPath(path: String): Projection[Seq] = {
- val ast = JsonPathParser(path)
-
- def convertPath(path: JsonPath.Path): Projection[Seq] = path match {
- case JsonPath.Root => value.toSeq
- case JsonPath.Selection(inner, proj) => convertPath(inner) / convertProjection(proj)
- }
- def convertProjection(proj: JsonPath.Projection): Projection[Seq] =
- proj match {
- case JsonPath.ByField(name) => field(name).toSeq
- case JsonPath.ByIndex(i) => element(i).toSeq
- case JsonPath.AllElements => elements
- case JsonPath.ByPredicate(pred) => filter(convertPredicate(pred))
- }
- def convertPredicate(pred: JsonPath.Predicate): JsPred = pred match {
- case op: JsonPath.BinOpPredicate =>
- val f1 = convertExpr(op.expr1)
- val f2 = convertSimpleExpr(op.expr2)
-
- js => {
- val v2 = f2(js)
- f1(js).right.forall(_.forall(v1 => op.predicate(v1, v2)))
- }
-
- case JsonPath.Exists(path) =>
- js => convertPath(path).retr(js).isRight
- }
- def convertExpr(expr: JsonPath.Expr): JsValue => Validated[Seq[JsValue]] = expr match {
- case JsonPath.PathExpr(path) => js => convertPath(path).retr(js)
- case simple: JsonPath.SimpleExpr => js => Right(Seq(convertSimpleExpr(simple)(js)))
- }
- def convertSimpleExpr(expr: JsonPath.SimpleExpr): JsValue => JsValue = expr match {
- case JsonPath.Constant(x) => _ => x
- }
-
- convertPath(ast)
- }
} \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/JsonPathIntegration.scala b/src/main/scala/cc/spray/json/lenses/JsonPathIntegration.scala
new file mode 100644
index 0000000..2586b08
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/JsonPathIntegration.scala
@@ -0,0 +1,44 @@
+package cc.spray.json
+package lenses
+
+trait JsonPathIntegration { self: ScalarLenses with SeqLenses =>
+
+ def fromPath(path: String): Projection[Seq] =
+ fromPath(JsonPathParser(path))
+
+ def fromPath(ast: JsonPath.Path): Projection[Seq] = {
+ def convertPath(path: JsonPath.Path): Projection[Seq] = path match {
+ case JsonPath.Root => value.toSeq
+ case JsonPath.Selection(inner, proj) => convertPath(inner) / convertProjection(proj)
+ }
+ def convertProjection(proj: JsonPath.Projection): Projection[Seq] =
+ proj match {
+ case JsonPath.ByField(name) => field(name).toSeq
+ case JsonPath.ByIndex(i) => element(i).toSeq
+ case JsonPath.AllElements => elements
+ case JsonPath.ByPredicate(pred) => filter(convertPredicate(pred))
+ }
+ def convertPredicate(pred: JsonPath.Predicate): JsPred = pred match {
+ case op: JsonPath.BinOpPredicate =>
+ val f1 = convertExpr(op.expr1)
+ val f2 = convertSimpleExpr(op.expr2)
+
+ js => {
+ val v2 = f2(js)
+ f1(js).right.forall(_.forall(v1 => op.predicate(v1, v2)))
+ }
+
+ case JsonPath.Exists(path) =>
+ js => convertPath(path).retr(js).isRight
+ }
+ def convertExpr(expr: JsonPath.Expr): JsValue => Validated[Seq[JsValue]] = expr match {
+ case JsonPath.PathExpr(path) => js => convertPath(path).retr(js)
+ case simple: JsonPath.SimpleExpr => js => Right(Seq(convertSimpleExpr(simple)(js)))
+ }
+ def convertSimpleExpr(expr: JsonPath.SimpleExpr): JsValue => JsValue = expr match {
+ case JsonPath.Constant(x) => _ => x
+ }
+
+ convertPath(ast)
+ }
+}
diff --git a/src/main/scala/cc/spray/json/lenses/Operations.scala b/src/main/scala/cc/spray/json/lenses/Operations.scala
new file mode 100644
index 0000000..daa7065
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/Operations.scala
@@ -0,0 +1,29 @@
+package cc.spray.json
+package lenses
+
+trait Operations {
+ def set[T: JsonWriter](t: T): Operation = new Operation {
+ def apply(value: SafeJsValue): SafeJsValue =
+ // ignore existence of old value
+ Right(t.toJson)
+ }
+
+ trait MapOperation extends Operation {
+ def apply(value: JsValue): SafeJsValue
+
+ def apply(value: SafeJsValue): SafeJsValue = value.flatMap(apply)
+ }
+
+ def updated[T: MonadicReader : JsonWriter](f: T => T): Operation = new MapOperation {
+ def apply(value: JsValue): SafeJsValue =
+ value.as[T] map (v => f(v).toJson)
+ }
+
+ def append(update: Update): Operation = ???
+
+ def update(update: Update): Operation = ???
+
+ def extract[M[_], T](value: Projection[M])(f: M[T] => Update): Operation = ???
+}
+
+object Operations extends Operations \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/Ops.scala b/src/main/scala/cc/spray/json/lenses/Ops.scala
new file mode 100644
index 0000000..d962642
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/Ops.scala
@@ -0,0 +1,63 @@
+package cc.spray.json.lenses
+
+trait Ops[M[_]] {
+ def flatMap[T, U](els: M[T])(f: T => Seq[U]): Seq[U]
+
+ def allRight[T](v: Seq[Validated[T]]): Validated[M[T]]
+
+ def toSeq[T](v: Validated[M[T]]): Seq[Validated[T]]
+
+ def map[T, U](els: M[T])(f: T => U): Seq[U] =
+ flatMap(els)(v => Seq(f(v)))
+ }
+
+ object Ops {
+ implicit def idOps: Ops[Id] = new Ops[Id] {
+ def flatMap[T, U](els: T)(f: T => Seq[U]): Seq[U] = f(els)
+
+ def allRight[T](v: Seq[Validated[T]]): Validated[T] = v.head
+
+ def toSeq[T](v: Validated[T]): Seq[Validated[T]] = Seq(v)
+ }
+
+ implicit def optionOps: Ops[Option] = new Ops[Option] {
+ def flatMap[T, U](els: Option[T])(f: T => Seq[U]): Seq[U] =
+ els.toSeq.flatMap(f)
+
+ def allRight[T](v: Seq[Validated[T]]): Validated[Option[T]] =
+ v match {
+ case Nil => Right(None)
+ case Seq(Right(x)) => Right(Some(x))
+ case Seq(Left(e)) => Left(e)
+ }
+
+ def toSeq[T](v: Validated[Option[T]]): Seq[Validated[T]] = v match {
+ case Right(Some(x)) => Seq(Right(x))
+ case Right(None) => Nil
+ case Left(e) => Seq(Left(e))
+ }
+ }
+
+ implicit def seqOps: Ops[Seq] = new Ops[Seq] {
+ def flatMap[T, U](els: Seq[T])(f: T => Seq[U]): Seq[U] =
+ els.flatMap(f)
+
+ def allRight[T](v: Seq[Validated[T]]): Validated[Seq[T]] = {
+ def inner(l: List[Validated[T]]): Validated[List[T]] = l match {
+ case head :: tail =>
+ for {
+ headM <- head
+ tailM <- inner(tail)
+ } yield headM :: tailM
+ case Nil =>
+ Right(Nil)
+ }
+ inner(v.toList)
+ }
+
+ def toSeq[T](x: Validated[Seq[T]]): Seq[Validated[T]] = x match {
+ case Right(x) => x.map(Right(_))
+ case Left(e) => List(Left(e))
+ }
+ }
+ } \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/OptionLenses.scala b/src/main/scala/cc/spray/json/lenses/OptionLenses.scala
new file mode 100644
index 0000000..0eb90d8
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/OptionLenses.scala
@@ -0,0 +1,26 @@
+package cc.spray.json
+package lenses
+
+trait OptionLenses {
+ def find(pred: JsPred): OptProjection = new Proj[Option] {
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
+ case JsArray(elements) =>
+ elements.span(x => !pred(x)) match {
+ case (prefix, element :: suffix) =>
+ f(Right(element)) map (v => JsArray(prefix ::: v :: suffix))
+
+ // element not found, do nothing
+ case _ =>
+ Right(parent)
+ }
+ case e@_ => unexpected("Not a json array: " + e)
+ }
+
+ def retr: JsValue => Validated[Option[JsValue]] = {
+ case JsArray(elements) => Right(elements.find(pred))
+ case e@_ => unexpected("Not a json array: " + e)
+ }
+ }
+}
+
+object OptionLenses extends OptionLenses \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/Projection.scala b/src/main/scala/cc/spray/json/lenses/Projection.scala
new file mode 100644
index 0000000..fe44115
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/Projection.scala
@@ -0,0 +1,56 @@
+package cc.spray.json
+package lenses
+
+/**
+ * A projection combines read and update functions of UpdateLens and ReadLens into
+ * combinable chunks.
+ * @tparam M
+ */
+trait Projection[M[_]] extends UpdateLens with ReadLens[M] {
+ def /[M2[_], R[_]](next: Projection[M2])(implicit ev: Join[M2, M, R]): Projection[R]
+
+ def toSeq: Projection[Seq]
+
+ def ops: Ops[M]
+}
+
+/**
+ * This implements most of the methods of `Projection`. Implementors of a new type of projection
+ * must implement `retr` for the read side of the lens and `updated` for the update side of the lens.
+ */
+trait ProjectionImpl[M[_]] extends Projection[M] {
+ outer =>
+ def tryGet[T: MonadicReader](p: JsValue): Validated[M[T]] =
+ retr(p).flatMap(mapValue(_)(_.as[T]))
+
+ def get[T: MonadicReader](p: JsValue): M[T] =
+ tryGet[T](p).getOrThrow
+
+ def !(op: Operation): Update = new Update {
+ def apply(parent: JsValue): JsValue =
+ updated(op)(parent).getOrThrow
+ }
+
+ def is[U: MonadicReader](f: U => Boolean): JsPred = value =>
+ tryGet[U](value) exists (x => ops.map(x)(f).forall(identity))
+
+ def /[M2[_], R[_]](next: Projection[M2])(implicit ev: Join[M2, M, R]): Projection[R] = new ProjectionImpl[R] {
+ val ops: Ops[R] = ev.get(next.ops, outer.ops)
+
+ def retr: JsValue => Validated[R[JsValue]] = parent =>
+ for {
+ outerV <- outer.retr(parent)
+ innerV <- ops.allRight(outer.ops.flatMap(outerV)(x => next.ops.toSeq(next.retr(x))))
+ } yield innerV
+
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
+ outer.updated(_.flatMap(next.updated(f)))(parent)
+ }
+
+ def toSeq: Projection[Seq] = this / SeqLenses.asSeq
+
+ private[this] def mapValue[T](value: M[JsValue])(f: JsValue => Validated[T]): Validated[M[T]] =
+ ops.allRight(ops.map(value)(f))
+}
+
+abstract class Proj[M[_]](implicit val ops: Ops[M]) extends ProjectionImpl[M] \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/ReadLens.scala b/src/main/scala/cc/spray/json/lenses/ReadLens.scala
new file mode 100644
index 0000000..241a7f7
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/ReadLens.scala
@@ -0,0 +1,35 @@
+package cc.spray.json
+package lenses
+
+/**
+ * The read lens can extract child values out of a JsValue hierarchy. A read lens
+ * is parameterized with a type constructor. This allows to extracts not only scalar
+ * values but also sequences or optional values.
+ * @tparam M
+ */
+trait ReadLens[M[_]] {
+ /**
+ * Given a parent JsValue, tries to extract the child value.
+ * @return `Right(value)` if the projection succeeds. `Left(error)` if the projection
+ * fails.
+ */
+ def retr: JsValue => Validated[M[JsValue]]
+
+ /**
+ * Given a parent JsValue extracts and tries to convert the JsValue into
+ * a value of type `T`
+ */
+ def tryGet[T: MonadicReader](value: JsValue): Validated[M[T]]
+
+ /**
+ * Given a parent JsValue extracts and converts a JsValue into a value of
+ * type `T` or throws an exception.
+ */
+ def get[T: MonadicReader](value: JsValue): M[T]
+
+ /**
+ * Lifts a predicate for a converted value for this lens up to the
+ * parent level.
+ */
+ def is[U: MonadicReader](f: U => Boolean): JsPred
+} \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/ScalarLenses.scala b/src/main/scala/cc/spray/json/lenses/ScalarLenses.scala
new file mode 100644
index 0000000..05d8657
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/ScalarLenses.scala
@@ -0,0 +1,61 @@
+package cc.spray.json
+package lenses
+
+trait ScalarLenses {
+ def field(name: String): ScalarProjection = new Proj[Id] {
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
+ for {
+ res <- f(getField(parent))
+ }
+ yield JsObject(fields = parent.asJsObject.fields + (name -> res))
+
+ def retr: JsValue => SafeJsValue = v =>
+ getField(v)
+
+ def getField(v: JsValue): SafeJsValue = asObj(v) flatMap {
+ o =>
+ o.fields.get(name).getOrError("Expected field '%s' in '%s'" format(name, v))
+ }
+
+ def asObj(v: JsValue): Validated[JsObject] = v match {
+ case o: JsObject =>
+ Right(o)
+ case e@_ =>
+ unexpected("Not a json object: " + e)
+ }
+ }
+
+ def element(idx: Int): ScalarProjection = new Proj[Id] {
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
+ case JsArray(elements) =>
+ if (idx < elements.size) {
+ val (headEls, element :: tail) = elements.splitAt(idx)
+ f(Right(element)) map (v => JsArray(headEls ::: v :: tail))
+ } else
+ unexpected("Too little elements in array: %s size: %d index: %d" format(parent, elements.size, idx))
+ case e@_ =>
+ unexpected("Not a json array: " + e)
+ }
+
+ def retr: JsValue => SafeJsValue = {
+ case a@JsArray(elements) =>
+ if (idx < elements.size)
+ Right(elements(idx))
+ else
+ outOfBounds("Too little elements in array: %s size: %d index: %d" format(a, elements.size, idx))
+ case e@_ => unexpected("Not a json array: " + e)
+ }
+ }
+
+ /**
+ * The identity projection which operates on the current element itself
+ */
+ val value: ScalarProjection = new Proj[Id] {
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue =
+ f(Right(parent))
+
+ def retr: JsValue => SafeJsValue = x => Right(x)
+ }
+}
+
+object ScalarLenses extends ScalarLenses \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/SeqLenses.scala b/src/main/scala/cc/spray/json/lenses/SeqLenses.scala
new file mode 100644
index 0000000..58fc4e3
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/SeqLenses.scala
@@ -0,0 +1,44 @@
+package cc.spray.json
+package lenses
+
+trait SeqLenses {
+ val asSeq: SeqProjection = new Proj[Seq] {
+ def updated(f: Operation)(parent: JsValue): SafeJsValue =
+ f(Right(parent))
+
+ def retr: JsValue => Validated[Seq[JsValue]] = x => Right(Seq(x))
+ }
+
+ val elements: SeqProjection = new Proj[Seq] {
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
+ case JsArray(elements) =>
+ ops.allRight(elements.map(x => f(Right(x)))).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)
+ }
+ }
+
+ /**Alias for `elements`*/
+ def * = elements
+
+ def filter(pred: JsPred): SeqProjection = new Proj[Seq] {
+ def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue = parent match {
+ case JsArray(elements) =>
+ ops.allRight(elements.map(x => if (pred(x)) f(Right(x)) else Right(x))).map(JsArray(_: _*))
+
+ 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)
+ }
+ }
+}
+
+object SeqLenses extends SeqLenses \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/UpdateLens.scala b/src/main/scala/cc/spray/json/lenses/UpdateLens.scala
new file mode 100644
index 0000000..c532b15
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/UpdateLens.scala
@@ -0,0 +1,32 @@
+package cc.spray.json
+package lenses
+
+trait Update extends (JsValue => JsValue) {
+ outer =>
+ def apply(value: JsValue): JsValue
+
+ def &&(next: Update): Update = new Update {
+ def apply(value: JsValue): JsValue = next(outer(value))
+ }
+}
+
+/**
+ * The UpdateLens is the central interface for updating a child element somewhere
+ * deep down a hierarchy of a JsValue.
+ */
+trait UpdateLens {
+ /**
+ * Applies function `f` on the child of the `parent` denoted by this UpdateLens
+ * and returns a `Right` of the parent with the child element updated.
+ *
+ * The value passed to `f` may be `Left(e)` if the child could not be found
+ * in which case particular operations may still succeed. Function `f` may return
+ * `Left(error)` in case the operation fails.
+ *
+ * `updated` returns `Left(error)` if the update operation or any of any intermediate
+ * projections fail.
+ */
+ def updated(f: Operation)(parent: JsValue): SafeJsValue
+
+ def !(op: Operation): Update
+} \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/lenses/package.scala b/src/main/scala/cc/spray/json/lenses/package.scala
new file mode 100644
index 0000000..10d3f30
--- /dev/null
+++ b/src/main/scala/cc/spray/json/lenses/package.scala
@@ -0,0 +1,79 @@
+package cc.spray.json
+
+package object lenses {
+ type JsPred = JsValue => Boolean
+ type Id[T] = T
+ type Validated[T] = Either[Exception, T]
+ type SafeJsValue = Validated[JsValue]
+
+ type Operation = SafeJsValue => SafeJsValue
+
+ type ScalarProjection = Projection[Id]
+ type OptProjection = Projection[Option]
+ type SeqProjection = Projection[Seq]
+
+ def ??? = sys.error("NYI")
+ def unexpected(message: String) = Left(new RuntimeException(message))
+ def outOfBounds(message: String) = Left(new IndexOutOfBoundsException(message))
+
+ implicit def rightBiasEither[A, B](e: Either[A, B]): Either.RightProjection[A, B] = e.right
+
+ case class GetOrThrow[B](e: Either[Throwable, B]) {
+ def getOrThrow: B = e match {
+ case Right(b) => b
+ case Left(e) => throw e
+ }
+ }
+
+ implicit def orThrow[B](e: Either[Throwable, B]): GetOrThrow[B] = GetOrThrow(e)
+
+ trait MonadicReader[T] {
+ def read(js: JsValue): Validated[T]
+ }
+
+ object MonadicReader {
+ implicit def safeMonadicReader[T: JsonReader]: MonadicReader[T] = new MonadicReader[T] {
+ def read(js: JsValue): Validated[T] =
+ safe(js.convertTo[T])
+ }
+ }
+
+ def safe[T](body: => T): Validated[T] =
+ try {
+ Right(body)
+ } catch {
+ case e: Exception => Left(e)
+ }
+
+ case class ValidateOption[T](option: Option[T]) {
+ def getOrError(message: => String): Validated[T] = option match {
+ case Some(t) => Right(t)
+ case None => unexpected(message)
+ }
+ }
+
+ implicit def validateOption[T](o: Option[T]): ValidateOption[T] = ValidateOption(o)
+
+ case class RichJsValue(value: JsValue) {
+ def update(updater: Update): JsValue = updater(value)
+
+ def update[T: JsonWriter, M[_]](lens: UpdateLens, pValue: T): JsValue =
+ lens ! Operations.set(pValue) apply value
+
+ // This can't be simplified because we don't want the type constructor
+ // of projection to appear in the type paramater list.
+ def extract[T: MonadicReader](p: Projection[Id]): T =
+ p.get[T](value)
+
+ def extract[T: MonadicReader](p: Projection[Option]): Option[T] =
+ p.get[T](value)
+
+ def extract[T: MonadicReader](p: Projection[Seq]): Seq[T] =
+ p.get[T](value)
+
+ def as[T: MonadicReader]: Validated[T] =
+ implicitly[MonadicReader[T]].read(value)
+ }
+
+ implicit def updatable(value: JsValue): RichJsValue = RichJsValue(value)
+} \ No newline at end of file