|
Play Framework/Scala example source code file (JsonValidSpec.scala)
The JsonValidSpec.scala Play Framework example source code
/*
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
*/
package play.api.libs.json
import org.specs2.mutable._
import play.api.libs.json._
import play.api.libs.json.Json._
import scala.util.control.Exception._
import java.text.ParseException
import play.api.data.validation.ValidationError
import play.api.libs.functional.syntax._
object JsonValidSpec extends Specification {
"JSON reads" should {
"validate simple types" in {
JsString("string").validate[String] must equalTo(JsSuccess("string"))
JsNumber(5).validate[Int] must equalTo(JsSuccess(5))
JsNumber(5L).validate[Long] must equalTo(JsSuccess(5L))
JsNumber(5).validate[Short] must equalTo(JsSuccess(5))
JsNumber(123.5).validate[Float] must equalTo(JsSuccess(123.5))
JsNumber(123456789123456.56).validate[Double] must equalTo(JsSuccess(123456789123456.56))
JsBoolean(true).validate[Boolean] must equalTo(JsSuccess(true))
JsString("123456789123456.56").validate[BigDecimal] must equalTo(JsSuccess(BigDecimal(123456789123456.56)))
JsNumber(123456789123456.56).validate[BigDecimal] must equalTo(JsSuccess(BigDecimal(123456789123456.567891234)))
JsNumber(123456789.56).validate[java.math.BigDecimal] must equalTo(JsSuccess(new java.math.BigDecimal("123456789.56")))
JsString("123456789123456.56").validate[java.math.BigDecimal] must equalTo(JsSuccess(new java.math.BigDecimal("123456789123456.56")))
}
"invalidate wrong simple type conversion" in {
JsString("string").validate[Long] must equalTo(JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsnumber")))))
JsNumber(5).validate[String] must equalTo(JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsstring")))))
JsBoolean(false).validate[Double] must equalTo(JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsnumber")))))
}
"validate simple numbered type conversion" in {
JsNumber(5).validate[Double] must equalTo(JsSuccess(5.0))
JsNumber(5.123).validate[Int] must equalTo(JsSuccess(5))
JsNumber(BigDecimal(5)).validate[Double] must equalTo(JsSuccess(5.0))
JsNumber(5.123).validate[BigDecimal] must equalTo(JsSuccess(BigDecimal(5.123)))
}
"return JsResult with correct values for isSuccess and isError" in {
JsString("s").validate[String].isSuccess must beTrue
JsString("s").validate[String].isError must beFalse
JsString("s").validate[Long].isSuccess must beFalse
JsString("s").validate[Long].isError must beTrue
}
"validate JsObject to Map" in {
Json.obj("key1" -> "value1", "key2" -> "value2").validate[Map[String, String]] must equalTo(JsSuccess(Map("key1" -> "value1", "key2" -> "value2")))
Json.obj("key1" -> 5, "key2" -> 3).validate[Map[String, Int]] must equalTo(JsSuccess(Map("key1" -> 5, "key2" -> 3)))
Json.obj("key1" -> 5.123, "key2" -> 3.543).validate[Map[String, Float]] must equalTo(JsSuccess(Map("key1" -> 5.123F, "key2" -> 3.543F)))
Json.obj("key1" -> 5.123, "key2" -> 3.543).validate[Map[String, Double]] must equalTo(JsSuccess(Map("key1" -> 5.123, "key2" -> 3.543)))
}
"invalidate JsObject to Map with wrong type conversion" in {
Json.obj("key1" -> "value1", "key2" -> "value2", "key3" -> "value3").validate[Map[String, Int]] must equalTo(
JsError(Seq(
JsPath \ "key1" -> Seq(ValidationError("error.expected.jsnumber")),
JsPath \ "key2" -> Seq(ValidationError("error.expected.jsnumber")),
JsPath \ "key3" -> Seq(ValidationError("error.expected.jsnumber"))
))
)
Json.obj("key1" -> "value1", "key2" -> 5, "key3" -> true).validate[Map[String, Int]] must equalTo(
JsError(Seq(
JsPath \ "key1" -> Seq(ValidationError("error.expected.jsnumber")),
JsPath \ "key3" -> Seq(ValidationError("error.expected.jsnumber"))
))
)
}
"validate JsArray to List" in {
Json.arr("alpha", "beta", "delta").validate[List[String]] must equalTo(JsSuccess(List("alpha", "beta", "delta")))
Json.arr(123, 567, 890).validate[List[Int]] must equalTo(JsSuccess(List(123, 567, 890)))
Json.arr(123.456, 567.123, 890.654).validate[List[Int]] must equalTo(JsSuccess(List(123, 567, 890)))
Json.arr(123.456, 567.123, 890.654).validate[List[Double]] must equalTo(JsSuccess(List(123.456, 567.123, 890.654)))
}
"invalidate JsArray to List with wrong type conversion" in {
Json.arr("alpha", "beta", "delta").validate[List[Int]] must equalTo(
JsError(Seq(
JsPath(0) -> Seq(ValidationError("error.expected.jsnumber")),
JsPath(1) -> Seq(ValidationError("error.expected.jsnumber")),
JsPath(2) -> Seq(ValidationError("error.expected.jsnumber"))
))
)
Json.arr("alpha", 5, true).validate[List[Int]] must equalTo(
JsError(Seq(
JsPath(0) -> Seq(ValidationError("error.expected.jsnumber")),
JsPath(2) -> Seq(ValidationError("error.expected.jsnumber"))
))
)
}
"validate JsArray of stream to List" in {
JsArray(Stream("alpha", "beta", "delta") map JsString.apply).validate[List[String]] must equalTo(JsSuccess(List("alpha", "beta", "delta")))
}
"invalidate JsArray of stream to List with wrong type conversion" in {
JsArray(Stream(JsNumber(1), JsString("beta"), JsString("delta"), JsNumber(4), JsString("five"))).validate[List[Int]] must equalTo(
JsError(Seq(
JsPath(1) -> Seq(ValidationError("error.expected.jsnumber")),
JsPath(2) -> Seq(ValidationError("error.expected.jsnumber")),
JsPath(4) -> Seq(ValidationError("error.expected.jsnumber"))
))
)
JsArray(Stream(JsString("alpha"), JsNumber(5), JsBoolean(true))).validate[List[Int]] must equalTo(
JsError(Seq(
JsPath(0) -> Seq(ValidationError("error.expected.jsnumber")),
JsPath(2) -> Seq(ValidationError("error.expected.jsnumber"))
))
)
}
"validate Dates" in {
val d = new java.util.Date()
val df = new java.text.SimpleDateFormat("yyyy-MM-dd")
val dd = df.parse(df.format(d))
Json.toJson[java.util.Date](dd).validate[java.util.Date] must beEqualTo(JsSuccess(dd))
JsNumber(dd.getTime).validate[java.util.Date] must beEqualTo(JsSuccess(dd))
val dj = new org.joda.time.DateTime()
val dfj = org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd")
val ddj = org.joda.time.DateTime.parse(dfj.print(dj), dfj)
Json.toJson[org.joda.time.DateTime](ddj).validate[org.joda.time.DateTime] must beEqualTo(JsSuccess(ddj))
JsNumber(ddj.getMillis).validate[org.joda.time.DateTime] must beEqualTo(JsSuccess(ddj))
val ldj = org.joda.time.LocalDate.parse(dfj.print(dj), dfj)
Json.toJson[org.joda.time.LocalDate](ldj).validate[org.joda.time.LocalDate] must beEqualTo(JsSuccess(ldj))
val ds = new java.sql.Date(dd.getTime())
Json.toJson[java.sql.Date](ds).validate[java.sql.Date] must beEqualTo(JsSuccess(dd))
JsNumber(dd.getTime).validate[java.sql.Date] must beEqualTo(JsSuccess(dd))
// very poor test to do really crappy java date APIs
// TODO ISO8601 test doesn't work on CI platform...
/*val c = java.util.Calendar.getInstance()
c.setTime(new java.util.Date(d.getTime - d.getTime % 1000))
val tz = c.getTimeZone().getOffset(c.getTime.getTime).toInt / 3600000
val js = JsString(
"%04d-%02d-%02dT%02d:%02d:%02d%s%02d:00".format(
c.get(java.util.Calendar.YEAR),
c.get(java.util.Calendar.MONTH) + 1,
c.get(java.util.Calendar.DAY_OF_MONTH),
c.get(java.util.Calendar.HOUR_OF_DAY),
c.get(java.util.Calendar.MINUTE),
c.get(java.util.Calendar.SECOND),
if(tz>0) "+" else "-",
tz
)
)
js.validate[java.util.Date](Reads.IsoDateReads) must beEqualTo(JsSuccess(c.getTime))*/
}
"validate UUID" in {
"validate correct UUIDs" in {
val uuid = java.util.UUID.randomUUID()
Json.toJson[java.util.UUID](uuid).validate[java.util.UUID] must beEqualTo(JsSuccess(uuid))
}
"reject malformed UUIDs" in {
JsString("bogus string").validate[java.util.UUID].recoverTotal {
e => "error"
} must beEqualTo("error")
}
"reject well-formed but incorrect UUIDS in strict mode" in {
JsString("0-0-0-0-0").validate[java.util.UUID](Reads.uuidReader(true)).recoverTotal {
e => "error"
} must beEqualTo("error")
}
}
"validate Enums" in {
object Weekdays extends Enumeration {
val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}
val json = Json.obj("day1" -> Weekdays.Mon, "day2" -> "tue", "day3" -> 3)
(json.validate((__ \ "day1").read(Reads.enumNameReads(Weekdays))).asOpt must beSome(Weekdays.Mon)) and
(json.validate((__ \ "day2").read(Reads.enumNameReads(Weekdays))).asOpt must beNone) and
(json.validate((__ \ "day3").read(Reads.enumNameReads(Weekdays))).asOpt must beNone)
}
"Can reads with nullable" in {
val json = Json.obj("field" -> JsNull)
val resultPost = json.validate( (__ \ "field").read(Reads.optionWithNull[String]) )
resultPost.get must equalTo(None)
}
}
"JSON JsResult" should {
"recover from error" in {
JsNumber(123).validate[String].recover{
case JsError(e) => "error"
} must beEqualTo(JsSuccess("error"))
JsNumber(123).validate[String].recoverTotal{
e => "error"
} must beEqualTo("error")
JsNumber(123).validate[Int].recoverTotal{
e => 0
} must beEqualTo(123)
}
}
"JSON caseclass/tuple validation" should {
case class User(name: String, age: Int)
"validate simple reads" in {
JsString("alphabeta").validate[String] must equalTo(JsSuccess("alphabeta"))
}
"validate simple constraints" in {
JsString("alphabeta").validate[String](Reads.minLength(5)) must equalTo(JsSuccess("alphabeta"))
}
"test JsPath.create" in {
val obj = JsPath.createObj(
JsPath \ "toto" \ "toto1" -> JsString("alpha"),
JsPath \ "titi" \ "titi1" -> JsString("beta"),
JsPath \ "titi" \ "titi2" -> JsString("beta2")
)
success
}
"validate simple case class reads/writes" in {
val bobby = User("bobby", 54)
implicit val userReads = { import Reads.path._
(
at(JsPath \ "name")(Reads.minLength[String](5))
and
at(JsPath \ "age")(Reads.min(40))
)(User) }
implicit val userWrites = { import Writes.path._
(
at[String](JsPath \ "name")
and
at[Int](JsPath \ "age")
)(unlift(User.unapply)) }
val js = Json.toJson(bobby)
js.validate[User] must equalTo(JsSuccess(bobby))
}
"validate simple case class format" in {
val bobby = User("bobby", 54)
implicit val userFormats = { import Format.path._; import Format.constraints._
(
at(JsPath \ "name")(Format(Reads.minLength[String](5), of[String]))
and
at(JsPath \ "age")(Format(Reads.min(40), of[Int]))
)(User, unlift(User.unapply)) }
val js = Json.toJson(bobby)
js.validate[User] must equalTo(JsSuccess(bobby))
}
"validate simple case class format" in {
val bobby = User("bobby", 54)
implicit val userFormats = { import Format.path._; import Format.constraints._
(
(__ \ "name").rw(Reads.minLength[String](5), of[String])
and
(__ \ "age").rw(Reads.min(40), of[Int])
) apply (User, unlift(User.unapply))
}
val js = Json.toJson(bobby)
js.validate[User] must equalTo(JsSuccess(bobby))
}
"JsObject tupled reads" in {
implicit val dataReads: Reads[(String, Int)] = { import Reads.path._
(
at[String]( __ \ "uuid" ) and
at[Int]( __ \ "nb" )
).tupled
}
val js = Json.obj(
"uuid" -> "550e8400-e29b-41d4-a716-446655440000",
"nb" -> 654
)
js.validate[(String, Int)] must equalTo(JsSuccess("550e8400-e29b-41d4-a716-446655440000" -> 654))
}
"JsObject tupled reads new syntax" in {
implicit val dataReads: Reads[(String, Int)] = (
( __ \ "uuid" ).read[String] and
( __ \ "nb" ).read[Int]
).tupled
val js = Json.obj(
"uuid" -> "550e8400-e29b-41d4-a716-446655440000",
"nb" -> 654
)
js.validate[(String, Int)] must equalTo(JsSuccess("550e8400-e29b-41d4-a716-446655440000" -> 654))
}
"JsObject tupled writes" in {
implicit val dataWrites: Writes[(String, Int)] = (
( __ \ "uuid" ).write[String] and
( __ \ "nb" ).write[Int]
).tupled
val js = Json.obj(
"uuid" -> "550e8400-e29b-41d4-a716-446655440000",
"nb" -> 654
)
Json.toJson("550e8400-e29b-41d4-a716-446655440000" -> 654) must equalTo(js)
}
"JsObject tupled format" in {
implicit val dataFormat: Format[(String, Int)] = (
( __ \ "uuid" ).format[String] and
( __ \ "nb" ).format[Int]
).tupled
val js = Json.obj(
"uuid" -> "550e8400-e29b-41d4-a716-446655440000",
"nb" -> 654
)
Json.toJson("550e8400-e29b-41d4-a716-446655440000" -> 654) must equalTo(js)
js.validate[(String, Int)] must equalTo(JsSuccess("550e8400-e29b-41d4-a716-446655440000" -> 654))
}
"Format simpler syntax without constraints" in {
val bobby = User("bobby", 54)
implicit val userFormats = {
(
(__ \ 'name).format[String]
and
(__ \ 'age).format[Int]
)(User, unlift(User.unapply)) }
val js = Json.toJson(bobby)
js.validate[User] must equalTo(JsSuccess(bobby))
}
"Format simpler syntax with constraints" in {
val bobby = User("bobby", 54)
implicit val userFormat = (
(__ \ 'name).format(Reads.minLength[String](5))
and
(__ \ 'age).format(Reads.min(40))
)(User, unlift(User.unapply))
val js = Json.toJson(bobby)
js.validate[User] must equalTo(JsSuccess(bobby))
}
"Compose reads" in {
val js = Json.obj(
"field1" -> "alpha",
"field2" -> 123L,
"field3" -> Json.obj("field31" -> "beta", "field32"-> 345)
)
val reads1 = (__ \ 'field3).json.pick
val reads2 = ((__ \ 'field32).read[Int] and (__ \ 'field31).read[String]).tupled
js.validate(reads1 andThen reads2).get must beEqualTo(345 -> "beta")
}
"Apply min/max correctly on any numeric type" in {
case class Numbers(i: Int, l: Long, f: Float, d: Double, bd: BigDecimal)
implicit val numbersFormat = (
(__ \ 'i).format[Int](Reads.min(5) andKeep Reads.max(100)) and
(__ \ 'l).format[Long](Reads.min(5L) andKeep Reads.max(100L)) and
(__ \ 'f).format[Float](Reads.min(13.0F) andKeep Reads.max(14.0F)) and
(__ \ 'd).format[Double](Reads.min(0.1) andKeep Reads.max(1.0)) and
(__ \ 'bd).format[BigDecimal](Reads.min(BigDecimal(5)) andKeep Reads.max(BigDecimal(100)))
)(Numbers.apply _, unlift(Numbers.unapply))
val ok = Numbers(42, 55L, 13.5F, 0.3, BigDecimal(33.5))
val fail = Numbers(42, 55L, 10.5F, 1.3, BigDecimal(33.5))
val jsOk = Json.toJson(ok)
val jsFail = Json.toJson(fail)
jsOk.validate[Numbers] must equalTo(JsSuccess(ok))
jsFail.validate[Numbers] must equalTo(
JsError((__ \ 'f), ValidationError("error.min", 13.0F)) ++
JsError((__ \ 'd), ValidationError("error.max", 1.0))
)
}
}
"JSON generators" should {
"Build JSON from JSON Reads" in {
import Reads._
val js0 = Json.obj(
"key1" -> "value1",
"key2" -> Json.obj(
"key21" -> 123,
"key23" -> true,
"key24" -> "blibli"
),
"key3" -> "alpha"
)
val js = Json.obj(
"key1" -> "value1",
"key2" -> Json.obj(
"key21" -> 123,
"key22" -> Json.obj("key222" -> "blabla"),
"key23" -> true,
"key24" -> "blibli"
),
"key3" -> Json.arr("alpha", "beta", "gamma")
)
val dt = (new java.util.Date).getTime()
def func = { JsNumber(dt + 100) }
val jsonTransformer = (
(__ \ "key1").json.pickBranch and
(__ \ "key2").json.pickBranch(
(
(__ \ "key22").json.update( (__ \ "key222").json.pick ) and
(__ \ "key233").json.copyFrom( (__ \ "key23").json.pick )
).reduce
) and
(__ \ "key3").json.pickBranch[JsArray]( pure(Json.arr("delta")) ) and
(__ \ "key4").json.put(
Json.obj(
"key41" -> 345,
"key42" -> "alpha",
"key43" -> func
)
)
).reduce
val res = Json.obj(
"key1" -> "value1",
"key2" -> Json.obj(
"key21" -> 123,
"key22" -> "blabla",
"key23" -> true,
"key24" -> "blibli",
"key233" -> true
),
"key3" -> Json.arr("delta"),
"key4" -> Json.obj("key41" -> 345, "key42" -> "alpha", "key43" -> func)
)
js0.validate(jsonTransformer) must beEqualTo(
//JsError( (__ \ 'key3), "error.expected.jsarray" ) ++
JsError( (__ \ 'key2 \ 'key22), "error.path.missing" )
)
js.validate(jsonTransformer) must beEqualTo(JsSuccess(res))
}
}
"JSON Reads" should {
"manage nullable/option" in {
case class User(name: String, email: String, phone: Option[String])
implicit val UserReads = (
(__ \ 'name).read[String] and
(__ \ 'coords \ 'email).read(Reads.email) and
(__ \ 'coords \ 'phone).readNullable(Reads.minLength[String](8))
)(User)
Json.obj(
"name" -> "john",
"coords" -> Json.obj(
"email" -> "john@xxx.yyy",
"phone" -> "0123456789"
)
).validate[User] must beEqualTo(
JsSuccess(User("john", "john@xxx.yyy", Some("0123456789")))
)
Json.obj(
"name" -> "john",
"coords" -> Json.obj(
"email" -> "john@xxx.yyy",
"phone2" -> "0123456789"
)
).validate[User] must beEqualTo(
JsSuccess(User("john", "john@xxx.yyy", None))
)
Json.obj(
"name" -> "john",
"coords" -> Json.obj(
"email" -> "john@xxx.yyy"
)
).validate[User] must beEqualTo(
JsSuccess(User("john", "john@xxx.yyy", None))
)
Json.obj(
"name" -> "john",
"coords" -> Json.obj(
"email" -> "john@xxx.yyy",
"phone" -> JsNull
)
).validate[User] must beEqualTo(
JsSuccess(User("john", "john@xxx.yyy", None))
)
Json.obj(
"name" -> "john",
"coords2" -> Json.obj(
"email" -> "john@xxx.yyy",
"phone" -> "0123456789"
)
).validate[User] must beEqualTo(
JsError(Seq(
__ \ 'coords \ 'phone -> Seq(ValidationError("error.path.missing")),
__ \ 'coords \ 'email -> Seq(ValidationError("error.path.missing"))
))
)
}
"report correct path for validation errors" in {
case class User(email: String, phone: Option[String])
implicit val UserReads = (
(__ \ 'email).read(Reads.email) and
(__ \ 'phone).readNullable(Reads.minLength[String](8))
)(User)
Json.obj("email" -> "john").validate[User] must beEqualTo(JsError(__ \ "email", ValidationError("error.email")))
Json.obj("email" -> "john.doe@blibli.com", "phone" -> "4").validate[User] must beEqualTo(JsError(__ \ "phone", ValidationError("error.minLength", 8)))
}
"mix reads constraints" in {
case class User(id: Long, email: String, age: Int)
implicit val UserReads = (
(__ \ 'id).read[Long] and
(__ \ 'email).read( Reads.email andKeep Reads.minLength[String](5) ) and
(__ \ 'age).read( Reads.max(55) or Reads.min(65) )
)(User)
Json.obj( "id" -> 123L, "email" -> "john.doe@blibli.com", "age" -> 50).validate[User] must beEqualTo(JsSuccess(User(123L, "john.doe@blibli.com", 50)))
Json.obj( "id" -> 123L, "email" -> "john.doe@blibli.com", "age" -> 60).validate[User] must beEqualTo(JsError((__ \ 'age), ValidationError("error.max", 55)) ++ JsError((__ \ 'age), ValidationError("error.min", 65)))
Json.obj( "id" -> 123L, "email" -> "john.doe", "age" -> 60).validate[User] must beEqualTo(JsError((__ \ 'email), ValidationError("error.email")) ++ JsError((__ \ 'age), ValidationError("error.max", 55)) ++ JsError((__ \ 'age), ValidationError("error.min", 65)))
}
"verifyingIf reads" in {
implicit val TupleReads: Reads[(String, JsObject)] = (
(__ \ 'type).read[String] and
(__ \ 'data).read(
Reads.verifyingIf[JsObject]{ case JsObject(fields) => !fields.isEmpty }(
((__ \ "title").read[String] and
(__ \ "created").read[java.util.Date]).tupled
)
)
).tupled
val d = (new java.util.Date()).getTime()
Json.obj("type" -> "coucou", "data" -> Json.obj()).validate(TupleReads) must beEqualTo(JsSuccess("coucou" -> Json.obj()))
Json.obj("type" -> "coucou", "data" -> Json.obj( "title" -> "blabla", "created" -> d)).validate(TupleReads) must beEqualTo(JsSuccess("coucou" -> Json.obj( "title" -> "blabla", "created" -> d)))
Json.obj("type" -> "coucou", "data" -> Json.obj( "title" -> "blabla")).validate(TupleReads) must beEqualTo(JsError( __ \ "data" \ "created", "error.path.missing"))
}
"recursive reads" in {
case class User(id: Long, name: String, friend: Option[User] = None)
implicit lazy val UserReads: Reads[User] = (
(__ \ 'id).read[Long] and
(__ \ 'name).read[String] and
(__ \ 'friend).lazyReadNullable(UserReads)
)(User)
val js = Json.obj(
"id" -> 123L,
"name" -> "bob",
"friend" -> Json.obj("id" -> 124L, "name" -> "john", "friend" -> JsNull)
)
js.validate[User] must beEqualTo(JsSuccess(User(123L, "bob", Some(User(124L, "john", None)))))
val js2 = Json.obj(
"id" -> 123L,
"name" -> "bob",
"friend" -> Json.obj("id" -> 124L, "name" -> "john")
)
js2.validate[User] must beEqualTo(JsSuccess(User(123L, "bob", Some(User(124L, "john", None)))))
success
}
"recursive writes" in {
import Writes.constraints._
case class User(id: Long, name: String, friend: Option[User] = None)
implicit lazy val UserWrites: Writes[User] = (
(__ \ 'id).write[Long] and
(__ \ 'name).write[String] and
(__ \ 'friend).lazyWriteNullable(UserWrites)
)(unlift(User.unapply))
val js = Json.obj(
"id" -> 123L,
"name" -> "bob",
"friend" -> Json.obj("id" -> 124L, "name" -> "john")
)
Json.toJson(User(123L, "bob", Some(User(124L, "john", None)))) must beEqualTo(js)
success
}
"recursive formats" in {
import Format.constraints._
case class User(id: Long, name: String, friend: Option[User] = None)
implicit lazy val UserFormats: Format[User] = (
(__ \ 'id).format[Long] and
(__ \ 'name).format[String] and
(__ \ 'friend).lazyFormatNullable(UserFormats)
)(User, unlift(User.unapply))
val js = Json.obj(
"id" -> 123L,
"name" -> "bob",
"friend" -> Json.obj("id" -> 124L, "name" -> "john")
)
js.validate[User] must beEqualTo(JsSuccess(User(123L, "bob", Some(User(124L, "john", None)))))
Json.toJson(User(123L, "bob", Some(User(124L, "john", None)))) must beEqualTo(js)
success
}
"lots of fields to read" in {
val myReads = (
(__ \ 'field1).read[String] and
(__ \ 'field2).read[Long] and
(__ \ 'field3).read[Float] and
(__ \ 'field4).read[Boolean] and
(__ \ 'field5).read[List[String]] and
(__ \ 'field6).read[String] and
(__ \ 'field7).read[String] and
(__ \ 'field8).read[String] and
(__ \ 'field9).read[String] and
(__ \ 'field10).read[String] and
(__ \ 'field11).read[String] and
(__ \ 'field12).read[String]
).tupled
Json.obj(
"field1" -> "val1",
"field2" -> 123L,
"field3" -> 123.456F,
"field4" -> true,
"field5" -> Json.arr("alpha", "beta"),
"field6" -> "val6",
"field7" -> "val7",
"field8" -> "val8",
"field9" -> "val9",
"field10" -> "val10",
"field11" -> "val11",
"field12" -> "val12"
).validate(myReads) must beEqualTo(
JsSuccess(( "val1", 123L, 123.456F, true, List("alpha", "beta"), "val6", "val7", "val8", "val9", "val10", "val11", "val12"))
)
}
"single field case class" in {
case class Test(field: String)
val myFormat = (__ \ 'field).format[String].inmap(Test, unlift(Test.unapply))
myFormat.reads(Json.obj("field" -> "blabla")) must beEqualTo(JsSuccess(Test("blabla"), __ \ 'field))
myFormat.reads(Json.obj()) must beEqualTo(JsError( __ \ 'field, "error.path.missing" ) )
myFormat.writes(Test("blabla")) must beEqualTo(Json.obj("field" -> "blabla"))
}
"reduce Reads[JsObject]" in {
import Reads._
val myReads: Reads[JsObject] = (
(__ \ 'field1).json.pickBranch and
(__ \ 'field2).json.pickBranch
).reduce
val js0 = Json.obj("field1" -> "alpha")
val js = js0 ++ Json.obj("field2" -> Json.obj("field21" -> 123, "field22" -> true))
val js2 = js ++ Json.obj("field3" -> "beta")
js.validate(myReads) must beEqualTo(JsSuccess(js))
js2.validate(myReads) must beEqualTo(JsSuccess(js))
js0.validate(myReads) must beEqualTo(JsError(__ \ 'field2, "error.path.missing"))
}
"reduce Reads[JsArray]" in {
import Reads._
val myReads: Reads[JsArray] = (
(__ \ 'field1).json.pick[JsString] and
(__ \ 'field2).json.pick[JsNumber] and
(__ \ 'field3).json.pick[JsBoolean]
).reduce[JsValue, JsArray]
val js0 = Json.obj("field1" -> "alpha")
val js = js0 ++ Json.obj("field2" -> 123L, "field3" -> false)
val js2 = js ++ Json.obj("field4" -> false)
js.validate(myReads) must beEqualTo(JsSuccess(Json.arr( "alpha", 123L, false)))
js2.validate(myReads) must beEqualTo(JsSuccess(Json.arr( "alpha", 123L, false)))
js0.validate(myReads) must beEqualTo(JsError(__ \ 'field2, "error.path.missing") ++ JsError(__ \ 'field3, "error.path.missing"))
}
"reduce Reads[JsArray] no type" in {
import Reads._
val myReads: Reads[JsArray] = (
(__ \ 'field1).json.pick and
(__ \ 'field2).json.pick and
(__ \ 'field3).json.pick
).reduce
val js0 = Json.obj("field1" -> "alpha")
val js = js0 ++ Json.obj("field2" -> 123L, "field3" -> false)
val js2 = js ++ Json.obj("field4" -> false)
js.validate(myReads) must beEqualTo(JsSuccess(Json.arr( "alpha", 123L, false)))
js2.validate(myReads) must beEqualTo(JsSuccess(Json.arr( "alpha", 123L, false)))
js0.validate(myReads) must beEqualTo(JsError(__ \ 'field2, "error.path.missing") ++ JsError(__ \ 'field3, "error.path.missing"))
}
"serialize JsError to flat json" in {
val jserr = JsError( Seq(
(__ \ 'field1 \ 'field11) -> Seq(
ValidationError("msg1.msg11", "arg11", 123L, 123.456F),
ValidationError("msg2.msg21.msg22", 456, 123.456, true, 123)
),
(__ \ 'field2 \ 'field21) -> Seq(
ValidationError("msg1.msg21", "arg1", Json.obj("test" -> "test2")),
ValidationError("msg2", "arg1", "arg2")
)
))
val flatJson = Json.obj(
"obj.field1.field11" -> Json.arr(
Json.obj(
"msg" -> "msg1.msg11",
"args" -> Json.arr("arg11",123,123.456F)
),
Json.obj(
"msg" ->"msg2.msg21.msg22",
"args" -> Json.arr(456,123.456,true,123)
)
),
"obj.field2.field21" -> Json.arr(
Json.obj(
"msg" -> "msg1.msg21",
"args" -> Json.arr("arg1", Json.obj("test" -> "test2"))
),
Json.obj(
"msg" -> "msg2",
"args" -> Json.arr("arg1","arg2")
)
)
)
JsError.toFlatJson(jserr) should beEqualTo(flatJson)
}
"prune json" in {
import Reads._
val js = Json.obj(
"field1" -> "alpha",
"field2" -> Json.obj("field21" -> 123, "field22" -> true, "field23" -> "blabla"),
"field3" -> "beta"
)
val res = Json.obj(
"field1" -> "alpha",
"field2" -> Json.obj("field22" -> true),
"field3" -> "beta"
)
val myReads: Reads[JsObject] = (
(__ \ 'field1).json.pickBranch and
(__ \ 'field2).json.pickBranch(
(__ \ 'field21).json.prune andThen (__ \ 'field23).json.prune
) and
(__ \ 'field3).json.pickBranch
).reduce
js.validate(myReads) must beEqualTo(JsSuccess(res))
}
}
"JSON Writes" should {
"manage option" in {
import Writes._
case class User(email: String, phone: Option[String])
implicit val UserWrites = (
(__ \ 'email).write[String] and
(__ \ 'phone).writeNullable[String]
)(unlift(User.unapply))
Json.toJson(User("john.doe@blibli.com", None)) must beEqualTo(Json.obj("email" -> "john.doe@blibli.com"))
Json.toJson(User("john.doe@blibli.com", Some("12345678"))) must beEqualTo(Json.obj("email" -> "john.doe@blibli.com", "phone" -> "12345678"))
}
"join" in {
val joinWrites = (
(__ \ 'alpha).write[JsString] and
(__ \ 'beta).write[JsValue]
).join
joinWrites.writes(JsString("toto")) must beEqualTo(Json.obj("alpha" -> "toto", "beta" -> "toto"))
val joinWrites2 = (
(__ \ 'alpha).write[JsString] and
(__ \ 'beta).write[JsValue] and
(__ \ 'gamma).write[JsString] and
(__ \ 'delta).write[JsValue]
).join
joinWrites2.writes(JsString("toto")) must beEqualTo(Json.obj("alpha" -> "toto", "beta" -> "toto", "gamma" -> "toto", "delta" -> "toto"))
}
}
"JSON Format" should {
"manage option" in {
import Reads._
import Writes._
case class User(email: String, phone: Option[String])
implicit val UserFormat = (
(__ \ 'email).format(email) and
(__ \ 'phone).formatNullable(Format(minLength[String](8), Writes.of[String]))
)(User, unlift(User.unapply))
Json.obj("email" -> "john").validate[User] must beEqualTo(JsError(__ \ "email", ValidationError("error.email")))
Json.obj("email" -> "john.doe@blibli.com", "phone" -> "4").validate[User] must beEqualTo(JsError(__ \ "phone", ValidationError("error.minLength", 8)))
Json.obj("email" -> "john.doe@blibli.com", "phone" -> "12345678").validate[User] must beEqualTo(JsSuccess(User("john.doe@blibli.com", Some("12345678"))))
Json.obj("email" -> "john.doe@blibli.com").validate[User] must beEqualTo(JsSuccess(User("john.doe@blibli.com", None)))
Json.toJson(User("john.doe@blibli.com", None)) must beEqualTo(Json.obj("email" -> "john.doe@blibli.com"))
Json.toJson(User("john.doe@blibli.com", Some("12345678"))) must beEqualTo(Json.obj("email" -> "john.doe@blibli.com", "phone" -> "12345678"))
}
}
"JsResult" should {
"be usable in for-comprehensions" in {
val res = JsSuccess("foo")
val x = for {
s <- res
if s.size < 5
} yield 42
x must equalTo (JsSuccess(42))
}
"be a functor" in {
"JsSuccess" in {
val res1: JsResult[String] = JsSuccess("foo", JsPath(List(KeyPathNode("bar"))))
res1.map(identity) must equalTo (res1)
}
"JsError" in {
val res2: JsResult[String] = JsError(Seq(JsPath(List(KeyPathNode("bar"))) -> Seq(ValidationError("baz.bah"))))
res2.map(identity) must equalTo (res2)
}
}
}
}
Other Play Framework source code examplesHere is a short list of links related to this Play Framework JsonValidSpec.scala source code file: |
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.