I'm performing a standard mapping of JSON to a case class using PlayJson. I'd like to transform the value that gets mapped to a member, Test.foo below, if the validation succeeds. Is it possible to work that into the definition of a Reads converter?
val json = .....
case class Test(foo:String, bar:String)
val readsTest: Reads[Test] = (
(__ \ "foo").read[String](minLength(5)) and // And I want to transform this value if the validation succeeds
(__ \ "bar").read[String](minLength(10))
)(Test.apply _)
json.validate[Test] match {
case s: JsSuccess[Test] => s.get
case e: JsError => false
}
Reads.map can do just that, for example, say we want to reverse the value of foo field, then we could call .map(v => v.reverse) on the Reads like so
(__ \ "foo").read[String](minLength[String](5)).map(v => v.reverse)
Here is a working example
val json =
"""
|{
| "foo": "abcdefghijkl",
| "bar": "012345678910"
|}
|""".stripMargin
case class Test(foo: String, bar: String)
val readsTest: Reads[Test] = (
(__ \ "foo").read[String](minLength[String](5)).map(v => v.reverse)
(__ \ "bar").read[String](minLength[String](10))
)(Test.apply _)
Json.parse(json).validate[Test](readsTest)
which outputs
JsSuccess(Test(lkjihgfedcba,012345678910),)
Related
I am trying to update a json value present within a json using Scala play framework.Instead of updating the value it is appending the value.
val newJsonString = """{"P123": 25}"""
val jsonStringAsJsValue = Json.parse("""{"counter_holders": {"Peter": 25}}""")
//jsonStringAsJsValue: play.api.libs.json.JsValue = {"counter_holders":{"Peter":25}}
val jsonTransformer = (__ \"counter_holders" ).json.update(__.read[JsValue].map{o => Json.parse(newJsonString)})
jsonStringAsJsValue.transform(jsonTransformer).get.as[JsValue]
//Now getting this jsvalue
//play.api.libs.json.JsValue = {"counter_holders":{"Peter":25,"P123":25}}
//But need this jsvalue
//play.api.libs.json.JsValue = {"counter_holders":{"P123":25}}
Any help on this will be really nice.
Quoting from the update method docs:
(__ \ 'key).json.update(reads) is the most complex Reads[JsObject]
but the most powerful:
copies the whole JsValue => A
applies the passed Reads[A] on JsValue => B
deep merges both JsValues (A ++ B) so B overwrites A identical branches Please note that if you have prune a branch in B, it is still
in A so you'll see it in the result Example:
{{{ val js = Json.obj("key1" -> "value1", "key2" -> "value2")
js.validate(__.json.update((__ \ 'key3).json.put(JsString("value3"))))
=> JsSuccess({"key1":"value1","key2":"value2","key3":"value3"},) }}}
Therefore the behaviour you see is as expected. If you want to take that approach, of updating using the path, you can use the method prune. For example you can do:
val newJsonString = """{"P123": 25}"""
val jsonStringAsJsValue = Json.parse("""{"counter_holders": {"Peter": 25}}""")
//jsonStringAsJsValue: play.api.libs.json.JsValue = {"counter_holders":{"Peter":25}}
val jsonTransformer = (__ \"counter_holders" ).json
.update(__.read[JsValue].map{o => Json.parse(newJsonString)})
val jsonTransformerDelete = (__ \"counter_holders" \ "Peter" ).json.prune
jsonStringAsJsValue.transform(jsonTransformer).flatMap(_.transform(jsonTransformerDelete)) match {
case JsSuccess(value, _) =>
println(value)
case JsError(errors) =>
println(errors)
}
which will produce the wanted behaviour. You can find it in scastie.
I'd like to use PlayJson to only validate multiple fields of some json and not map it to a custom object. I Only care about the Yes Or No answer to the validation criteria. Is it possible to use PlayJson in that way? So far I have something like,
val json = .....
val reads = (JsPath \ "foo").read[String](min(5)) and
(JsPath \ "bar").read[String](max(10))
json.validate["I ONLY WANT TO VALIDATE NOT MAP"](reads) match {
case s: JsSuccess => true
case e: JsError => false
}
Thank you Stack Overflow community.
Instead of deserialising to a case class model via Reads[MyModel] we can deserialise to a tuple via Reads[(String, String)] like so
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
val reads = (
(JsPath \ "foo").read[String](minLength[String](5)) and
(JsPath \ "bar").read[String](minLength[String](10))
).tupled
val json = Json.parse(
"""
|{
| "foo": "abcde",
| "bar": "woohoowoohoo",
| "zar": 42
|}
|""".stripMargin)
json.validate(reads).isSuccess
which outputs
res0: Boolean = true
Note how we called tupled method when creating the reader, and isSuccess to get a boolean out of validation process.
https://scalafiddle.io/sf/JBjdt2Y/0
So I have following JSON:
{
"senderEmail" : "sender#email.com",
"recipientEmails" : ["first#email.com", "second#email.com"]
}
and would like to map it to case class:
case class Payload (senderEmail: String, recipientEmails: Seq[String])
using Play's Json Reads with email validator.
While it's trivial for a senderEmail, I'm having trouble with recipientEmails since it's both Seq and email so this will not work:
implicit val payloadRead: Reads[Payload] = (
(JsPath \ "senderEmail").read[String](Reads.email) and
(JsPath \ "recipientEmails").read[Seq[String]](Reads.seq))(Payload.apply _)
I'm getting overloaded method value read with alternatives.
So how can I combine both Reads.seq and Reads.email?
Just keep it simple ...
scala> import play.api.libs.json._
scala> import play.api.libs.functional.syntax._
scala> case class Payload (senderEmail: String, recipientEmails: Seq[String])
defined class Payload
scala> implicit val reads: Reads[Payload] = (
| (JsPath \ "senderEmail").read(Reads.email) and
| (JsPath \ "recipientEmails").read(Reads.seq(Reads.email))
| )(Payload.apply _)
reads: play.api.libs.json.Reads[Payload] = play.api.libs.json.Reads$$...
scala> Json.parse("""{
| "senderEmail" : "sender#email.com",
| "recipientEmails" : ["first#email.com", "second#email.com"]
| }""").validate[Payload]
res0: play.api.libs.json.JsResult[Payload] = JsSuccess(Payload(sender#email.com,Vector(first#email.com, second#email.com)),)
i have to check either the password field is alphanumeric or not and if not it will throw custom Validation error i am using play-framework but getting compile time error
value checkAlphanumeric is not a member of
play.api.libs.json.Reads[String]
- value checkAlphanumeric is not a member of
play.api.libs.json.Reads[String]
i am unable to achive my desired outcome i am doing it wrong that's why i need here is the code
case class userUserSignUpValidation(firstName: String,
lastName: String,
email: String,
password: String) extends Serializable
object UserSignUpValidation {
val allNumbers = """\d*""".r
val allLetters = """[A-Za-z]*""".r
var validationErrorMsg=""
implicit val readDirectUser: Reads[DirectUserSignUpValidation] = (
(JsPath \ "firstName").read(minLength[String](1)) and
(JsPath \ "lastName").read(minLength[String](1)) and
(JsPath \ "email").read(email) and
(JsPath \ "password").read(minLength[String](8)checkAlphanumeric))(UserSignUpValidation.apply _)
def checkAlphanumeric(password:String)={
val allNumbers = """\d*""".r
val allLetters = """[A-Za-z]*""".r
val errors = password match {
case allNumbers() => Seq(ValidationError("Password is all numbers"))
case allLetters() => Seq(ValidationError("Password is all letters"))
case _ => Nil
}
}
i am getting the error on this line
(JsPath \ "password").read(minLength[String](8)checkAlphanumeric))(UserSignUpValidation.apply _)
what is the right way to implement an above scenario
Your problem is that you cannot use your checkAlphanumeric method that way. What you probably want is a filter on the Reads, so I would suggest doing something as follow (I changed the implementation for the check, using pre-existing methods):
implicit val readDirectUser: Reads[DirectUserSignUpValidation] = (
(JsPath \ "firstName").read(minLength[String](1)) and
(JsPath \ "lastName").read(minLength[String](1)) and
(JsPath \ "email").read(email) and
(JsPath \ "password").read(minLength[String](8).
filterNot(ValidationError("Password is all numbers"))(_.forall(_.isDigit)).
filterNot(ValidationError("Password is all letters"))(_.forall(_.isLetter))
)) (UserSignUpValidation.apply _)
Well I wonder why don't you run validations inside case class?
case class userUserSignUpValidation(firstName: String,
lastName: String,
email: String,
password: String) {
assert(!password.matches("""[A-Za-z]*""") && !password.matches("""\d*"""), "Invalid password")
// have other field validations here
}
And in you UserSignUpValidation use a implicit formatter like this:
object UserSignUpValidation {
implicit val userFormatter = JSON.format[userUserSignUpValidation]
// de-serialization code here
}
Scala newbie here.
I use Play to provide a json API for reading and writing a directory like structure. Therefore I use Scalaz.Tree, which provides ways of traversing, updating and rebuilding the Tree.
Formatting the Tree into json works well:
case class File(id: String = BSONObjectID.generate.toString(), name: String, noteBookId: String = null)
implicit val fileFormat: Format[File] = Json.format[File]
implicit def treeWrites: Writes[Tree[File]] =
new Writes[Tree[File]] {
def writes(o: Tree[File]) = o match {
case Node(file, children) => Json.obj(
"name" -> file.name,
"id" -> file.id,
"children" -> JsArray(children.map(Json.toJson(_))),
"notebookId" -> file.noteBookId
)
}
}
Reading json into a Tree however, fails
implicit def treeReads: Reads[Tree[File]] = (
//(__ \ "children").lazyRead(Reads.seq[File](treeReads)) and
(__ \ "children").read[Tree[File]] and
(__ \ "name").read[String] and
(__ \ "notebookid").read[String] and // <-- this is line 41, where the error message points at!!
(__ \ "id").read[String]
)(apply _)
implicit val treeFormat: Format[Tree[File]] = Format(treeReads, treeWrites)
The error I am getting:
[error] /home/dikken/Development/core-service-spaas/app/models/dirTree.scala:41: overloaded method value apply with alternatives:
[error] [B](f: B => (scalaz.Tree[model.treedir.File], String, String, String))(implicit fu: play.api.libs.functional.ContravariantFunctor[play.api.libs.json.Reads])play.api.libs.json.Reads[B] <and>
[error] [B](f: (scalaz.Tree[model.treedir.File], String, String, String) => B)(implicit fu: play.api.libs.functional.Functor[play.api.libs.json.Reads])play.api.libs.json.Reads[B]
[error] cannot be applied to ((=> Nothing) => scalaz.Tree[Nothing])
[error] (__ \ "id").read[String] and
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
Does this mean I have to pattern match on a case where I have a Tree of Nothing? And how excatly should I do that?
Any help appreciated! Tx!
I'm going to assume that apply _ is actually File.apply _, which cannot work here. File.apply accepts the parameters of the case class File (of which there are three). With JSON combinators, it is trying to pass the four parameters above to File.apply, which does not mix. It also does not produce a Tree[File]. What you need to do is replace File.apply with a method that accepts (children, notebookid, name, id) as parameters, and produces a Tree[File].
Here's a somewhat crude one that will work:
def jsonToTree(children: Seq[Tree[File]], name: String, notebookid: String, id: String): Tree[File] =
Tree.node(File(id, name, notebookid), children.toStream)
The Reads will now look more like this:
implicit def treeReads: Reads[Tree[File]] = (
(__ \ "children").lazyRead[Seq[Tree[File]]](Reads.seq(treeReads)).orElse(Reads.pure(Nil)) and
(__ \ "name").read[String] and
(__ \ "notebookid").read[String] and
(__ \ "id").read[String]
)(jsonToTree _)
You were closer with the commented out line as well. Because this is a recursive structure, we need to use lazyRead.
Testing:
val js = Json.parse("""{
"id": "1",
"name": "test",
"notebookid": "abc",
"children": [
{
"id": "2",
"name": "test222",
"notebookid": "ijk"
},
{
"id": "3",
"name": "test333",
"notebookid": "xyz"
}
]
}""")
scala> val tree = js.as[Tree[File]]
tree: scalaz.Tree[File] = <tree>
scala> tree.rootLabel
res8: File = File(1,test,abc)
scala> tree.subForest
res9: Stream[scalaz.Tree[File]] = Stream(<tree>, ?)
This can also be done (certainly in different ways) without combinators, as well (provided there is an implicit Reads[File] available):
implicit def treeReads: Reads[Tree[File]] = new Reads[Tree[File]] {
def reads(js: JsValue): JsResult[Tree[File]] = {
js.validate[File] map { case file =>
(js \ "children").validate[Stream[Tree[File]]].fold(
_ => Tree.leaf(file),
children => Tree.node(file, children)
)
}
}
}