Scala JsObject to nested class - scala

I have this scenario im getting a list of Facebook feed(facebook api) to a list of json objects.
Than i want to deserialize to a list of class object.
First question is ,which data structure will be best practices ? (cass class,tratis or regular class)
This is how i did it , i created a list of Comments in the Post case class
case class Post(id: String, fromId: String, fromName: String, message: String, fullUrl: String, createdTime: String, updateTime: String ,List[Comment]) {
}
case class Comment(id: String, fromId: String, fromName: String, message: String, creationTime: String, likeCount: Int)
Then im mapping it like that
implicit val post = (
(__ \ "id").read[String] and
(__ \ "from").\("id").read[String] and
(__ \ "from").\("name").read[String] and
(__ \ "message").read[String] and
(__ \ "actions").\\("link").read[String] and
(__ \ "created_time").read[String] and
(__ \ "updated_time").read[String]
)(Post)
The second question is how can i map the list ??
thanks,
miki

Case classes looks good in this case.
You need to define the same implicit reader for comments and then change you reader for posts something like that:
implicit val post = 2
(__ \ "id").read[String] and
(__ \ "from").\("id").read[String] and
(__ \ "from").\("name").read[String] and
(__ \ "message").read[String] and
(__ \ "actions").\\("link").read[String] and
(__ \ "created_time").read[String] and
(__ \ "updated_time").read[String] and
(__ \ "comments").reads[List[Comment]]
)(Post)

Related

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
}

Play Framework: How to serialize raw fields to JSON with nested elements

I need to serialize a String and an Option[Boolean]:
val myWrites = (
(__ \ "box").write(
(
(__ \ "name").write[String] ~
(__ \ "default").writeNullable[Boolean]
).tupled
)
)
If Option[Boolean] is Some then I'd expect
{
"box": {
"name": "John",
"default": true
}
}
... while if Option[Boolean] is None I'd expect
{
"box": {
"name": "John"
}
}
Given the following variables...
val name = "John"
val default = Some(true)
... how do I pass them to the Writes? I've tried this:
myWrites.writes(name, defaul)
... but it doesn't compile:
No Json serializer found for type play.api.libs.functional.FunctionalBuilder[play.api.libs.json.OWrites]#CanBuild2[String,Option[Boolean]].
Try to implement an implicit Writes or Format for this type.
[error] (__ \ "box").write(
I think its just a typo in your writes. you have defaul vs default
I was able to use
import play.api.libs.json._
import play.api.libs.functional.syntax._
val myWrites = (
(__ \ "box").write(
(
(__ \ "name").write[String] ~
(__ \ "default").writeNullable[Boolean]
).tupled
)
)
myWrites.writes("hi",Some(true))
and I got back
res0: play.api.libs.json.JsObject = {"box":{"name":"hi","default":true}}

ReactiveMongo: Manage Sequence field

I'm developing an app with Play Framework and ReactiveMongo.
I want to write CRUD operations for a model field with type of Seq[Entities]
Here is my model:
case class Person(_id: Option[BSONObjectID],
email: String,
password: String,
education: Option[Education])
object Lawyer {
implicit val accountWrites: Writes[Person] = (
(JsPath \ "_id").writeNullable[BSONObjectID] and
(JsPath \ "email").write[String] and
(JsPath \ "password").write[String] and
(JsPath \ "education").writeNullable[Education]
)(unlift(Person.unapply))
implicit val accountReads: Reads[Person] = (
(JsPath \ "_id").readNullable[BSONObjectID].map(_.getOrElse(BSONObjectID.generate)).map(Some(_)) and
(JsPath \ "email").read[String] and
(JsPath \ "password").read[String] and
(JsPath \ "education").readNullable[Education]
)(Person.apply _)
case class Education(status: String, certificates: Option[Seq[Certificate]])
object Education {
implicit val educationFormat: Format[Education] = (
(JsPath \ "status").format[String] and
(JsPath \ "certificates").formatNullable[Seq[Certificate]]
)(Education.apply, unlift(Education.unapply))
}
case class Certificate(id: Option[String] = Some(Random.alphanumeric.take(12).mkString),
name: String,
licenseCode: Option[String],
link: Option[String],
date: Date)
object Certificate {
implicit val certificateFormat = Json.format[Certificate]
}
The questions are:
1) How I can POST the Certificate entity using a form?
Because when I use:
def createCertificate(email: String, certificate: Certificate) = {
val createCertificate = Json.obj(
"$set" -> Json.obj(
"education.certificates" -> certificate
)
)
collection.update(
Json.obj("email" -> email),
createCertificate
)
}
It creates object field {...} insted of array of objects [ {...}, ... ]
2) How I can DELETE the Certificate entity from the Seq by ID?
Thanks
1) I assume that you want createCertificate to add a single certificate to an (possibly empty) array of certificates instead of creating an array with a single certificate. In that case you can replace your $set operator with the $push operator:
val createCertificate = Json.obj(
"$push" -> Json.obj(
"education.certificates" -> certificate
)
)
2) Likewise, for removing an element you can use the $pull operator:
def removeCertificate(email: String, certificateId: String) = {
val removeCert = Json.obj(
"$pull" -> Json.obj(
"education.certificates" -> Json.obj("id" -> certificateId)
)
)
collection.update(
Json.obj("email" -> email),
removeCert
)
}

Play JSON Combinators

In Play 2.1 we use something like below to get a Creature Object out of a JSON through reads.
implicit val creatureReads = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean] and
(__ \ "weight").read[Float]
)(Creature.apply _)
Being relative new in Scala, I'm trying to understand if there is any other way to build the Creature object without using the Apply method? Would it be possible to have an anonymous function to create the object instead of relying on the apply?
I have use cases where most of the fields in my objects could be missing, but I would like to still build the object out of what I have. Is it better to just define one READ for the object and use readnullable for each of the fields?
I also could have complex conditionals, so would it be cleaner to just define custom functions to build it instead of trying to capture all cases in one Reader?
Yes of course, the apply method is just a method that takes all the case classes' parameters. This roughly translates to the following:
implicit val creatureReads = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean] and
(__ \ "weight").read[Float]
)((name: String, isDead: Boolean, weight: Float) => new Creature(name, isDead, weight))
For missing fields you should indeed use readNullable and wrap your classes fields to Option. If there are sensible defaults for your optional fields, you can use orElse(Reads.pure(value)) instead.
Let's say weight is optional and isDead is false by default:
implicit val creatureReads = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean].orElse(Reads.pure(false)) and
(__ \ "weight").readNullable[Float]
)(Creature.apply _)
Sometimes you don't even want to read something from JSON. In that case, one possibility is passing the value explicitly:
def creatureReads(createdAt: DateTime) = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean].orElse(Reads.pure(false)) and
(__ \ "weight").readNullable[Float] and
(__ \ "createdAt").read(createdAt)
)(Creature.apply _)
I find this to be much more readable:
implicit val createReads = new Reads[Creature] {
override def reads(json: JsValue): JsResult[Creature] = {
val creature = Creature(
name = (json \ "name").as[String],
isDead = (json \ "isDead").as[Boolean],
weight = (json \ "weight").as[Float]
)
JsSuccess(creature)
}
}