ReactiveMongo: Manage Sequence field - mongodb

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

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

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}}

Why should I choose combinators over class construction for JSON serialisation?

The Play Scala documentation shows how to serialise JSON in two ways, first:
implicit val locationWrites = new Writes[Location] {
def writes(location: Location) = Json.obj(
"lat" -> location.lat,
"long" -> location.long
)
}
Then with combinators:
implicit val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(unlift(Location.unapply))
What reasons should be considered to choose one approach over the other?