Permalink
Browse files

Adds lift-json-scalaz7 (Scalaz 7 support).

* Adds a new submodule for lift-json-scalaz7.
* Maintains lift-json-scalaz for Scalaz 6 support.
* Updated README to correspond to Scalaz 7.
  • Loading branch information...
1 parent 81f3ec1 commit 15461ae4b8a370194a594b23f1f02904acd5ddb4 Taylor Leese committed Mar 22, 2013
@@ -79,6 +79,6 @@ Add dependency to your SBT project description:
Links
-----
-* [More examples](https://github.com/lift/framework/tree/master/core/json-scalaz/src/test/scala/net/lifweb/json/scalaz)
+* [More examples](https://github.com/lift/framework/tree/master/core/json-scalaz/src/test/scala/net/liftweb/json/scalaz)
* [Scalaz](http://code.google.com/p/scalaz/)
* [Kleisli composition](http://www.haskell.org/hoogle/?hoogle=%28a+-%3E+m+b%29+-%3E+%28b+-%3E+m+c%29+-%3E+%28a+-%3E+m+c%29)
@@ -0,0 +1,88 @@
+Scalaz support for Lift JSON
+============================
+
+This project adds a type class to parse JSON:
+
+ trait JSON[A] {
+ def read(json: JValue): Result[A]
+ def write(value: A): JValue
+ }
+
+ type Result[+A] = ValidationNel[Error, A]
+
+Function 'read' returns an Applicative Functor, enabling parsing in an applicative style.
+
+Simple example
+--------------
+
+ scala> import scalaz._
+ scala> import Scalaz._
+ scala> import net.liftweb.json.scalaz.JsonScalaz._
+ scala> import net.liftweb.json._
+
+ scala> case class Address(street: String, zipCode: String)
+ scala> case class Person(name: String, age: Int, address: Address)
+
+ scala> val json = parse(""" {"street": "Manhattan 2", "zip": "00223" } """)
+ scala> (field[String]("street")(json) |@| field[String]("zip")(json)) { Address }
+ res0: Success(Address(Manhattan 2,00223))
+
+ scala> (field[String]("streets")(json) |@| field[String]("zip")(json)) { Address }
+ res1: Failure("no such field 'streets'")
+
+Notice the required explicit types when reading fields from JSON. The library comes with helpers which
+can lift functions with pure values into "parsing context". This works well with Scala's type inferencer:
+
+ scala> Address.applyJSON(field[String]("street"), field[String]("zip"))(json)
+ res2: Success(Address(Manhattan 2,00223))
+
+Function 'applyJSON' above lifts function
+
+ (String, String) => Address
+
+to
+
+ (JValue => Result[String], JValue => Result[String]) => (JValue => Result[Address])
+
+Example which adds a new type class instance
+--------------------------------------------
+
+ scala> implicit def addrJSONR: JSONR[Address] = Address.applyJSON(field[String]("street"), field[String]("zip"))
+
+ scala> val p = JsonParser.parse(""" {"name":"joe","age":34,"address":{"street": "Manhattan 2", "zip": "00223" }} """)
+ scala> Person.applyJSON(field[String]("name"), field[Int]("age"), field[Address]("address"))(p)
+ res0: Success(Person(joe,34,Address(Manhattan 2,00223)))
+
+Validation
+----------
+
+Applicative style parsing works nicely with validation and data conversion. It is easy to compose
+validations using a for comprehension.
+
+ def min(x: Int): Int => Result[Int] = (y: Int) =>
+ if (y < x) Fail("min", y + " < " + x) else y.success
+
+ def max(x: Int): Int => Result[Int] = (y: Int) =>
+ if (y > x) Fail("max", y + " > " + x) else y.success
+
+ val ageResult = (jValue: JValue) => for {
+ age <- field[Int]("age")(jValue)
+ _ <- min(16)(age)
+ _ <- max(60)(age)
+ } yield age
+
+ // Creates a function JValue => Result[Person]
+ Person.applyJSON(field[String]("name"), ageResult, field[Address]("address"))
+
+Installation
+------------
+
+Add dependency to your SBT project description:
+
+ val lift_json_scalaz = "net.liftweb" %% "lift-json-scalaz" % "XXX"
+
+Links
+-----
+
+* [More examples](https://github.com/lift/framework/tree/master/core/json-scalaz7/src/test/scala/net/liftweb/json/scalaz)
+* [Scalaz](http://code.google.com/p/scalaz/)
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2009-2010 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.json.scalaz
+
+import scalaz.ValidationNel
+import scalaz.Validation._
+import scalaz.std.option._
+import scalaz.std.list._
+import scalaz.syntax.traverse._
+import net.liftweb.json._
+import scala.collection.breakOut
+
+trait Base { this: Types =>
+ implicit def boolJSON: JSON[Boolean] = new JSON[Boolean] {
+ def read(json: JValue) = json match {
+ case JBool(b) => success(b)
+ case x => failure(UnexpectedJSONError(x, classOf[JBool])).toValidationNel
+ }
+
+ def write(value: Boolean) = JBool(value)
+ }
+
+ implicit def intJSON: JSON[Int] = new JSON[Int] {
+ def read(json: JValue) = json match {
+ case JInt(x) => success(x.intValue)
+ case x => failure(UnexpectedJSONError(x, classOf[JInt])).toValidationNel
+ }
+
+ def write(value: Int) = JInt(BigInt(value))
+ }
+
+ implicit def longJSON: JSON[Long] = new JSON[Long] {
+ def read(json: JValue) = json match {
+ case JInt(x) => success(x.longValue)
+ case x => failure(UnexpectedJSONError(x, classOf[JInt])).toValidationNel
+ }
+
+ def write(value: Long) = JInt(BigInt(value))
+ }
+
+ implicit def doubleJSON: JSON[Double] = new JSON[Double] {
+ def read(json: JValue) = json match {
+ case JDouble(x) => success(x)
+ case x => failure(UnexpectedJSONError(x, classOf[JDouble])).toValidationNel
+ }
+
+ def write(value: Double) = JDouble(value)
+ }
+
+ implicit def stringJSON: JSON[String] = new JSON[String] {
+ def read(json: JValue) = json match {
+ case JString(x) => success(x)
+ case x => failure(UnexpectedJSONError(x, classOf[JString])).toValidationNel
+ }
+
+ def write(value: String) = JString(value)
+ }
+
+ implicit def bigintJSON: JSON[BigInt] = new JSON[BigInt] {
+ def read(json: JValue) = json match {
+ case JInt(x) => success(x)
+ case x => failure(UnexpectedJSONError(x, classOf[JInt])).toValidationNel
+ }
+
+ def write(value: BigInt) = JInt(value)
+ }
+
+ implicit def jvalueJSON: JSON[JValue] = new JSON[JValue] {
+ def read(json: JValue) = success(json)
+ def write(value: JValue) = value
+ }
+
+ implicit def listJSONR[A: JSONR]: JSONR[List[A]] = new JSONR[List[A]] {
+ def read(json: JValue) = json match {
+ case JArray(xs) => {
+ xs.map(fromJSON[A]).sequence[({type λ[α]=ValidationNel[Error, α]})#λ, A]
+ }
+ case x => failure(UnexpectedJSONError(x, classOf[JArray])).toValidationNel
+ }
+ }
+ implicit def listJSONW[A: JSONW]: JSONW[List[A]] = new JSONW[List[A]] {
+ def write(values: List[A]) = JArray(values.map(x => toJSON(x)))
+ }
+
+ implicit def optionJSONR[A: JSONR]: JSONR[Option[A]] = new JSONR[Option[A]] {
+ def read(json: JValue) = json match {
+ case JNothing | JNull => success(None)
+ case x => fromJSON[A](x).map(some)
+ }
+ }
+ implicit def optionJSONW[A: JSONW]: JSONW[Option[A]] = new JSONW[Option[A]] {
+ def write(value: Option[A]) = value.map(x => toJSON(x)).getOrElse(JNull)
+ }
+
+ implicit def mapJSONR[A: JSONR]: JSONR[Map[String, A]] = new JSONR[Map[String, A]] {
+ def read(json: JValue) = json match {
+ case JObject(fs) =>
+ val r = fs.map(f => fromJSON[A](f.value).map(v => (f.name, v))).sequence[({type λ[α]=ValidationNel[Error, α]})#λ, (String, A)]
+ r.map(_.toMap)
+ case x => failure(UnexpectedJSONError(x, classOf[JObject])).toValidationNel
+ }
+ }
+ implicit def mapJSONW[A: JSONW]: JSONW[Map[String, A]] = new JSONW[Map[String, A]] {
+ def write(values: Map[String, A]) = JObject(values.map { case (k, v) => JField(k, toJSON(v)) }(breakOut))
+ }
+}
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2009-2010 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.json.scalaz
+
+import scalaz.{Equal, Kleisli, Monoid, Semigroup, Show, ValidationNel}
+import scalaz.Validation._
+import scalaz.std.option._
+import net.liftweb.json._
+
+trait Types {
+ type Result[+A] = ValidationNel[Error, A]
+
+ sealed trait Error
+ case class UnexpectedJSONError(was: JValue, expected: Class[_ <: JValue]) extends Error
+ case class NoSuchFieldError(name: String, json: JValue) extends Error
+ case class UncategorizedError(key: String, desc: String, args: List[Any]) extends Error
+
+ case object Fail {
+ def apply[A](key: String, desc: String, args: List[Any]): Result[A] =
+ failure(UncategorizedError(key, desc, args)).toValidationNel
+
+ def apply[A](key: String, desc: String): Result[A] =
+ failure(UncategorizedError(key, desc, Nil)).toValidationNel
+ }
+
+ implicit def JValueShow[A <: JValue]: Show[A] = new Show[A] {
+ override def shows(json: A): String = compact(render(json))
+ }
+
+ implicit def JValueMonoid: Monoid[JValue] = Monoid.instance(_ ++ _, JNothing)
+ implicit def JValueSemigroup: Semigroup[JValue] = Semigroup.instance(_ ++ _)
+ implicit def JValueEqual: Equal[JValue] = Equal.equalA
+
+ trait JSONR[A] {
+ def read(json: JValue): Result[A]
+ }
+
+ trait JSONW[A] {
+ def write(value: A): JValue
+ }
+
+ trait JSON[A] extends JSONR[A] with JSONW[A]
+
+ implicit def Result2JSONR[A](f: JValue => Result[A]): JSONR[A] = new JSONR[A] {
+ def read(json: JValue) = f(json)
+ }
+
+ def fromJSON[A: JSONR](json: JValue): Result[A] = implicitly[JSONR[A]].read(json)
+ def toJSON[A: JSONW](value: A): JValue = implicitly[JSONW[A]].write(value)
+
+ def field[A: JSONR](name: String)(json: JValue): Result[A] = json match {
+ case JObject(fs) =>
+ fs.find(_.name == name)
+ .map(f => implicitly[JSONR[A]].read(f.value))
+ .orElse(implicitly[JSONR[A]].read(JNothing).fold(_ => none, x => some(success(x))))
+ .getOrElse(failure(NoSuchFieldError(name, json)).toValidationNel)
+ case x => failure(UnexpectedJSONError(x, classOf[JObject])).toValidationNel
+ }
+
+ def validate[A: JSONR](name: String): Kleisli[Result, JValue, A] = Kleisli(field[A](name))
+
+ def makeObj(fields: Traversable[(String, JValue)]): JObject =
+ JObject(fields.toList.map { case (n, v) => JField(n, v) })
+}
+
+object JsonScalaz extends Types with Lifting with Base with Tuples
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2009-2010 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.json.scalaz
+
+import scalaz.syntax.apply._
+import net.liftweb.json._
+
+trait Lifting { this: Types =>
+ implicit def Func2ToJSON[A: JSONR, B: JSONR, R](z: (A, B) => R) = new {
+ def applyJSON(a: JValue => Result[A], b: JValue => Result[B]): JValue => Result[R] =
+ (json: JValue) => (a(json) |@| b(json))(z)
+ }
+
+ implicit def Func3ToJSON[A: JSONR, B: JSONR, C: JSONR, R](z: (A, B, C) => R) = new {
+ def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C]): JValue => Result[R] =
+ (json: JValue) => (a(json) |@| b(json) |@| c(json))(z)
+ }
+
+ implicit def Func4ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, R](z: (A, B, C, D) => R) = new {
+ def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D]): JValue => Result[R] =
+ (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json))(z)
+ }
+
+ implicit def Func5ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, E: JSONR, R](z: (A, B, C, D, E) => R) = new {
+ def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D], e: JValue => Result[E]): JValue => Result[R] =
+ (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json) |@| e(json))(z)
+ }
+
+ implicit def Func6ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, E: JSONR, F: JSONR, R](z: (A, B, C, D, E, F) => R) = new {
+ def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D], e: JValue => Result[E], f: JValue => Result[F]): JValue => Result[R] =
+ (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json) |@| e(json) |@| f(json))(z)
+ }
+
+ implicit def Func7ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, E: JSONR, F: JSONR, G: JSONR, R](z: (A, B, C, D, E, F, G) => R) = new {
+ def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D], e: JValue => Result[E], f: JValue => Result[F], g: JValue => Result[G]): JValue => Result[R] =
+ (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json) |@| e(json) |@| f(json) |@| g(json))(z)
+ }
+
+ implicit def Func8ToJSON[A: JSONR, B: JSONR, C: JSONR, D: JSONR, E: JSONR, F: JSONR, G: JSONR, H: JSONR, R](z: (A, B, C, D, E, F, G, H) => R) = new {
+ def applyJSON(a: JValue => Result[A], b: JValue => Result[B], c: JValue => Result[C], d: JValue => Result[D], e: JValue => Result[E], f: JValue => Result[F], g: JValue => Result[G], h: JValue => Result[H]): JValue => Result[R] =
+ (json: JValue) => (a(json) |@| b(json) |@| c(json) |@| d(json) |@| e(json) |@| f(json) |@| g(json) |@| h(json))(z)
+ }
+}
Oops, something went wrong.

0 comments on commit 15461ae

Please sign in to comment.