Converting from JsResultException to JsError - scala

I have Json that may have spaces in it which I'd like to trim. I created this case class to parse with Json.parse(...).as[PersonalInfo] but the issue is that it fails on the first failed read and also doesn't return a JsError. How would I define the JsError case while also preserving the errors for every field that wasn't parsed correctly?
case class PersonalInfo(
email: String,
password: String,
firstName: String,
lastName: String
)
object PersonalInfo {
implicit val personalInfoReads = new Reads[PersonalInfo] {
override def reads(json: JsValue): JsResult[PersonalInfo] = {
val email = (json \ "email").as[String].trim
val password = (json \ "password").as[String].trim
val firstName = (json \ "firstName").as[String].trim
val lastName = (json \ "lastName").as[String].trim
JsSuccess(PersonalInfo(email, password, firstName, lastName))
}
}
}

See my answer here
In two words: do not use as in reads. In case of failed parsing it throws exception. Use validate. Example you can find by link above
Update
Well, if you want solution..
case class PersonalInfo(email: String,
password: String,
firstName: String,
lastName: String)
object PersonalInfo {
implicit val personalInfoReads = new Reads[PersonalInfo] {
override def reads(json: JsValue): JsResult[PersonalInfo] = {
for {
email <- (json \ "email").validate[String]
password <- (json \ "password").validate[String]
firstName <- (json \ "firstName").validate[String]
lastName <- (json \ "lastName").validate[String]
} yield PersonalInfo(
email.trim(),
password.trim(),
firstName.trim(),
lastName.trim()
)
}
}
}

My idea is to use the standard format (you can also use read) of play-json
And then add a function to get it trimmed:
case class PersonalInfo(
email: String,
password: String,
firstName: String,
lastName: String
) {
def trimmed() = PersonalInfo(
email.trim,
password.trim,
firstName.trim,
lastName.trim
)
}
object PersonalInfo {
implicit val customWrites: OFormat[PersonalInfo] = Json.format[PersonalInfo]
}
You can use it like that:
json.validate[PersonalInfo] match {
case JsSuccess(info: PersonalInfo, _) =>
info.trimmed()
case JsError(errors) =>
error("Other than RunAdapter: " + errors.toString())
}
The errors is a list of all validation errors.

Related

Play Json Validation is failing on Date field with Error: [error.expected.date.isoformat]

I have a JsonValidationError.
This is my model:
case class Account(id: Long, name: String, birthDate: LocalDate)
object Account {
implicit val accountFormat = Json.format[Account]
}
And this is part of my controller action:
def updateProfile = Action.async(parse.json) { implicit request =>
val res = request.body.validate[Account]
logger.info(request.body.toString)
logger.info(res.toString)
...
}
I have this output:
[info] c.a.AccountController - {"id":1,"name":"John","birthDate":"1983-02-11T23:00:00.000Z"}
[info] c.a.AccountController - JsError(List((/birthDate,List(JsonValidationError(List(error.expected.date.isoformat),ArraySeq(ParseCaseSensitive(false)(Value(Year,4,10,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2))[Offset(+HH:MM:ss,'Z')])))))
The data coming from the client seems to be valid, I don't understand that Error, I think the birthDate is in the isoFormat, so what can be the issue here ?
Please help.
The input has a different format than expected for java.time.LocalDate.
Try the following custom codec to make the issue gone:
case class Account(id: Long, name: String, birthDate: LocalDate)
object Account {
implicit val accountFormat: OFormat[Account] = {
implicit val customLocalDateFormat: Format[LocalDate] = Format(
Reads(js => JsSuccess(Instant.parse(js.as[String]).atZone(ZoneOffset.UTC).toLocalDate)),
Writes(d => JsString(d.atTime(LocalTime.of(23, 0)).atZone(ZoneOffset.UTC).toInstant.toString)))
Json.format[Account]
}
}
val json = """{"id":1,"name":"John","birthDate":"1983-02-11T23:00:00.000Z"}"""
val account = Json.parse(json).as[Account]
println(account)
println(Json.toJson(account))
An expected output of this code snippet is:
Account(1,John,1983-02-11)
{"id":1,"name":"John","birthDate":"1983-02-11T23:00:00Z"}

Using extractors to parse text files

I'm trying to improve a CSV parsing routine and feel that extractors could be useful here but can't figure them out. Suppose there's a file with user ids and emails:
1,alice#alice.com
2,bob#bob.com
3,carol#carol.com
If the User class is defined as case class User(id: Int, email: String) everything is pretty easy with something like
lines map { line =>
line split "," match {
case Array(id, email) => User(id.toInt, email)
}
}
What I don't understand is how to deal with the case where User class can have complex properties e.g
case class Email(username: String, host: string)
case class User(id: Int, email: Email)
You probably want to use a regular expression to extract the contents of the email address. Maybe something like this:
val lines = Vector(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com")
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val EmailRe = """(.*)#(.*\.com)""".r // substitute a real email address regex here
lines.map { line =>
line.split(",") match {
case Array(id, EmailRe(uname, host)) => User(id.toInt, Email(uname, host))
}
}
Here you go, an example using a custom Extractor.
// 1,Alice,21212,Baltimore,MD" -> User(1, Alice, Address(21212, Baltimore, MD))
Define a custom Extractor that creates the objects out of given String:
object UserExtractor {
def unapply(s: String) : Option[User] = try {
Some( User(s) )
}
catch {
// bettor handling of bad cases
case e: Throwable => None
}
}
Case classes to hold the data with a custom apply on Comapnion object on User:
case class Address(code: String, cit: String, county: String)
case class User(id: Int, name: String, address: Address)
object User {
def apply(s: String) : User = s.split(",") match {
case Array(id, name, code, city, county) => User(id.toInt, name, Address(code, city, county) )
}
}
Unapplying on a valid string (in the example valid means the correct number of fields).
"1,Alice,21212,Baltimore,MD" match { case UserExtractor(u) => u }
res0: User = User(1,Alice,Address(21212,Baltimore,MD))
More tests could be added with more custom apply methods.
I'd use a single RegexExtractor :
val lines = List(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com"
)
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val idAndEMail = """^([^,]+),([^#]+)#(.+)$""".r
and define a function that transforms a line to the an User :
def lineToUserWithMail(line: String) : Option[User] =
idAndEMail.findFirstMatchIn(line) map {
case userWithEmail(id,user,host) => User(id.toInt, Email(user,host) )
}
Applying the function to all lines
lines flatMap lineToUserWithMail
//res29: List[User] = List(User(1,Email(alice,alice.com)), User(2,Email(bob,bob.com)), User(3,Email(carol,carol.com)))
Alternatively you could implement custom Extractors on the case classe by adding an unnapply Method. But for that case it wouldn't be worth the pain.
Here is an example for unapply
class Email(user:String, host:String)
object Email {
def unapply(s: String) : Option[(String,String)] = s.split("#") match {
case Array(user, host) => Some( (user,host) )
case _ => None
}
}
"bob#bob.com" match {
case Email(u,h) => println( s"$u , $h" )
}
// prints bob , bob.com
A word of warning on using Regex to parse CSV-data. It's not as easy as you might think, i would recommend to use a CSV-Reader as http://supercsv.sourceforge.net/ which handles some nasty edge cases out of the box.

Password and password hash in a model and in db table

I have a table User(id, password_hash, ....) in db and a model for it:
case class User(
id: Pk[Long] = NotAssigned,
email: String,
password: Option[String] = None,
passwordHash: Option[String] = None
)
object User {
def create(newUser: User): Option[Long] = //.....
//on("password_hash" -> generatePasswordHash(newUser.password)
def generatePasswordHash(p: String) = //....
}
The point is Password field exists only the model User and is filled up only I register a new user:
val newUser = User(email = emailFromForm, password = Some(passwordFromForm))
I send to db only a hash of the password. Obviously, when I retrieve it from db, Password field in None but PasswordHash has a value.
I made Password and PasswordHash to be Option because I think they should be Options, shouldn't they? I'm not sure, though, whether it's right or wrong.
The question is my is this a good approach?
Why do you want to have User.password at all?
case class User(
id: Pk[Long] = NotAssigned,
email: String,
passwordHash: String
)
object User {
// or maybe Option[User] or Try[User]
def create(email: String, password: String): Option[Long] = {
val passwordHash = hashPassword(hash)
val newUser = User(email, passwordHash)
// save newUser to DB
}
// you may want to distinguish between "no such email" and "wrong password"
// in which case you'd have something like Either[PasswordCheckFailure, User]
def checkPassword(email: String, password: String): Option[User] = {
val possibleUser: Option[User] = // get user by email
possibleUser.filter(_.passwordHash = hashPassword(password))
}
private def hashPassword(password: String): String = ...
}
You may want to have a salt as well, see e.g. https://crackstation.net/hashing-security.htm. In this case you either store it in the same field as password or add another field:
case class User(
id: Pk[Long] = NotAssigned,
email: String,
passwordHash: String,
passwordSalt: String = // generate random string
)

Scala + Play -> Type mismatch; found : anorm.RowParser Required: anorm.ResultSetParser[Option[models.User]]

SOLUTION: I could not figure our how to return the non-exists for of Option[User] so in the case of not user found I create a dummy user object and reason on it from the controller (feels awful but working...):
from Application.scala
val loginForm = Form(
tuple(
"email" -> text,
"password" -> text
) verifying ("Invalid email or password", result => result match {
case (email, password) => (User.authenticate(email, password).map{_.id}.getOrElse(0) != 0)
})
)
AS OPPOSED TO:
val loginForm = Form(
tuple(
"email" -> text,
"password" -> text
) verifying ("Invalid email or password", result => result match {
case (email, password) => User.authenticate(email, password).isDefined
})
)
++++++++++++++++++ ORIGINAL 2 ++++++++++++++++++
Thanks for the advice! I have made some change and seem to be getting closer however I can't figure out how to return an undefinded Option[user]. I have also tried case _ => null, See below:
From User.scala
case class User(id: Int, email: String, name: String, password: String)
object User {
// -- Parsers
/**
* Parse a User from a ResultSet
*/
val userParser = {
get[Option[Int]]("uid")~
get[Option[String]]("email")~
get[Option[String]]("fname")~
get[Option[String]]("pbkval") map {
case (uid~email~name~pbkval) => validate(uid,email, name, pbkval)
}
}
/**
* Retrieve a User from email.
*/
def findByEmail(email: String): Option[User] = {
DB.withConnection { implicit connection =>
SQL("select * from get_pbkval({email})").on(
'email -> email
).as(userParser.singleOpt)
}
}
/**
* Authenticated user session start.
*/
def authenticate(email: String, password: String): Option[User] = {
DB.withConnection { implicit connection =>
SQL(
"""
select * from get_pbkval({email})
"""
).on(
'email -> email
).as(userParser.singleOpt)
}
}
/**
* Validate entry and create user object.
*/
def validate(uid: Option[Int], email: Option[String], fname: Option[String], pbkval: Option[String]): User = {
val uidInt : Int = uid.getOrElse(0)
val emailString: String = email.getOrElse(null)
val fnameString: String = fname.getOrElse(null)
val pbkvalString: String = pbkval.getOrElse(null)
User(uidInt, emailString, fnameString, pbkvalString)
}
I guess it is clear that I am not really getting something fundamental here.. I have read through http://www.playframework.org/modules/scala-0.9.1/anorm and searched around for hours.. any help would be much appreciated!
You didn't specify which rows to map. After your row mapper, put a * to signify which rows to map. While you're at it, I find it easier to define my row mapper in a separate val. Something like this.
val user = {
get[Option[Int]]("uid")~
get[Option[String]]("email")~
get[Option[String]]("fname")~
get[Option[String]]("pbkval") map {
case uid~email~name~password => validate(uid,email, name, password)
}
}
def authenticate(email: String, password: String): Option[User] = {
DB.withConnection { implicit connection =>
SQL(
"""
select * from get_pbkval({email})
"""
).on(
'email -> email
).as(user *)
}
}
def validate(uid: Option[Int], email: Option[String], fname: Option[String], pbkval: Option[String]): Option[User] = {
if (uid != None) {
val uidInt : Int = uid.getOrElse(0)
val emailString: String = email.getOrElse(null)
val fnameString: String = fname.getOrElse(null)
val pbkvalString: String = pbkval.getOrElse(null)
User(uidInt, emailString, fnameString, pbkvalString)
} else { return null}
}
Note the "as" method now has two arguments, your row mapper (which is now defined as a val "user", and a "*" signifying that you want to map all of the rows.

Issues Overloading CTOR's in Scala

Im having an issue over loading the constructors in Scala. Each time I try to pass a value for the over loaded CTOR I get the error
Example:
var client : Client = Client(*variable type List[String]()*);
Unspecified value parameter clientList.
My goal is to have an object created using 2 different data types. One a NodeSeq and the Other a List. Never Both. Am I over loading the CTOR correctly or is there a more efficient way to achieve my goal?
package api
import xml._
case class Client(clientNode: NodeSeq, clientList: List[String]) {
def this(clientNode: NodeSeq) = this(clientNode, null)
def this(clientList: List[String]) = this(null, clientList)
var firstName: String
var lastName: String
var phone: String
var street: String
var city: String
var state: String
var zip: String
var products = List[String]()
var serviceOrders = List[String]()
if (clientList == null) {
firstName = (clientNode \\ "firstname").text
lastName = (clientNode \\ "lastname").text
phone = (clientNode \\ "phone").text
street = (clientNode \\ "street").text
city = (clientNode \\ "city").text
state = (clientNode \\ "state").text
zip = (clientNode \\ "zip").text
(clientNode \\ "products").foreach(i => products = i.text :: products)
(clientNode \\ "serviceOrders").foreach(i => serviceOrders = i.text :: serviceOrders)
} else {
firstName = clientList(0)
lastName = clientList(1)
phone = clientList(2)
street = clientList(3)
city = clientList(4)
state = clientList(5)
zip = clientList(6)
}
override def toString(): String = {
return "Name : " + firstName + " " + lastName +
"\nAddress : " +
"\n\t" + street +
"\n\t" + city + ", " + state + " " + zip
}
}
You didn't post working code; you can't have undefined vars.
Anyway, the problem is that even though you have overridden the constructors, you have not overridden the builders in the companion object. Add this and it will work the way you want:
object Client {
def apply(clientNode: NodeSeq) = new Client(clientNode)
def apply(clientList: List[String]) = new Client(clientList)
}
(if you're using the REPL, be sure to use :paste to enter this along with the case class so you add to the default companion object instead of replacing it).
But a deeper problem is that this is not the way you should be solving the problem. You should define a trait that contains the data you want:
trait ClientData {
def firstName: String
def lastName: String
/* ... */
}
and inherit from it twice, once for each way to parse it:
class ClientFromList(cl: List[String]) extends ClientData {
val firstName = cl.head
. . .
}
or you could turn the NodeSeq into a list and parse it there, or various other things. This way you avoid exposing a bunch of vars which are probably not meant to be changed later.
Auxiliary constructors are for simple one-liners and aren't suitable in this case. More idiomatic way would be to define factory methods in a companion object:
case class Client(firstName: String,
lastName: String,
products: List[String] = Nil)
object Client {
import scala.xml.NodeSeq
def fromList(list: List[String]): Option[Client] =
list match {
case List(firstName, lastName) =>
Some(Client(firstName, lastName))
case _ => None
}
def fromXml(nodeSeq: NodeSeq): Option[Client] = {
def option(label: String) =
(nodeSeq \\ label).headOption.map(_.text)
def listOption(label: String) =
(nodeSeq \\ label).headOption.map {
_.map(_.text).toList
}
for {
firstName <- option("firstname")
lastName <- option("lastname")
products <- listOption("products")
} yield Client(firstName, lastName, products)
}
}
I also took the liberty of improving your code by eliminating mutability and making it generally more runtime safe.