Handling non-default nested Json Reads - scala

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)),)

Related

Transform Value In PlayJSon Mapping

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),)

Validation Only With Play Json

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

Scala Play readNullable can't read Map types

I am trying to use play.api.lib.json to convert a json to my object. But then this happend...
case class Foo(foo:Option[Map[String,String]])
case class Bar(bar:String,foo:Foo)
def barJsonToModel(foobarJson:JsValue):Bar = {
implicit val fooReads: Reads[Foo] = (
( JsPath \ "foo" ).readNullable[Map[String,String]]
)(Foo.apply _)
}
Expression of type Reads[Option[Map[String,String]]] doesn't comfort to expect type Reads[Foo]
You are using the functional syntax of play-json, but so an import is missing before:
import play.api.libs.functional.syntax._

How to convert JsValue to a Model Class with Scala in Play framework?

I'm trying to set up a JSON response that I got from a weather API to fit in a model class I defined in order to use it easily, but I can't get to do it.
Here is the class :
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Forecast(var main: String, var description: String, var temp: Int, var tempMin: Int, var tempMax: Int)
object Forecast {
implicit val forecastJsonFormat: Reads[Forecast] = (
(JsPath \ "weather" \\"main").read[String] and
(JsPath \ "weather" \\"description").read[String] and
(JsPath \ "main" \\"temp").read[Int] and
(JsPath \ "main" \\"temp_min").read[Int] and
(JsPath \ "main" \\"temp_max").read[Int]
) (Forecast.apply _)
}
and this is the code in the controller :
def weather = Action.async {
futureResponse.map(response => {
val jsonString = response.json.toString()
val jsonObject = Json.parse(jsonString)
// TODO: Create t [Forecast] Object which represents the response.json data to send it to the view below
Ok(views.html.weather(t))
})}
example of the response.json I'am getting :
{"coord":{"lon":37.62,"lat":55.75},"weather":[{"id":600,"main":"Snow","description":"light snow","icon":"13n"},{"id":701,"main":"Mist","description":"mist","icon":"50n"}],"base":"stations","main":{"temp":269.15,"pressure":1024,"humidity":92,"temp_min":268.15,"temp_max":270.15},"visibility":3100,"wind":{"speed":2,"deg":200},"clouds":{"all":90},"dt":1546266600,"sys":{"type":1,"id":9029,"message":0.0029,"country":"RU","sunrise":1546235954,"sunset":1546261585},"id":524901,"name":"Moscow","cod":200}
You have to change main to Seq[String] and description to Seq[String] and temp, tempMin, tempMax to Double
I used a different way to create the reads here, but this way will throw an exception if the format is different than the expected format.
case class Forecast(main: Seq[String], description: Seq[String], temp: Double, tempMin: Double, tempMax: Double)
object Forecast {
val reads = new Reads[Forecast] {
override def reads(json: JsValue): JsResult[Forecast] = {
val main = (json \ "weather" \\ "main").map(_.as[String]).toList
val description = (json \ "weather" \\ "description").map(_.as[String]).toList
val temp = (json \ "main" \ "temp").as[Double]
val tempMin = (json \ "main" \ "temp_min").as[Double]
val tempMax = (json \ "main" \ "temp_max").as[Double]
JsSuccess(Forecast(main, description, temp, tempMin, tempMax))
}
}
}
or you can use the same way you are using, but parse the list in different way:
val forecastJsonFormat: Reads[Forecast] = (
(JsPath \ "weather").read[List[Map[String, JsValue]]].map(_.map(_("main").as[String])) and
(JsPath \ "weather").read[List[Map[String, JsValue]]].map(_.map(_("description").as[String])) and
(JsPath \ "main" \ "temp").read[Double] and
(JsPath \ "main" \ "temp_min").read[Double] and
(JsPath \ "main" \ "temp_max").read[Double]
) (Forecast.apply _)
I finally got to do it and here is how :
In the model I have my case class defined and a companion object that parses the JSON response I got from the web API to my class arguments
Model code :
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Forecast(main: String, description: String, temp: Double, tempMin: Double, tempMax: Double)
object Forecast {
implicit val forecastReads: Reads[Forecast] = (
(JsPath \ "weather" \\ "main").read[String] and
(JsPath \ "weather" \\ "description").read[String] and
(JsPath \ "main" \ "temp").read[Double] and
(JsPath \ "main" \ "temp_min").read[Double] and
(JsPath \ "main" \ "temp_max").read[Double]
) (Forecast.apply _)
}
In the controller code I added a pattern matching and here it is !
Controller code :
def weather = Action.async {
futureResponse.map(response => {
val parseResult = Json.fromJson[Forecast](response.json)
parseResult match {
case JsSuccess(forecast, JsPath) => Ok(views.html.weather(forecast))
case JsError(error) => InternalServerError("Something went wrong!!") // Note that I'm not sure this result exists in Play...
}
})
}

how to check whether the given field is alphanumeric or not in PlayFramework

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
}