Hi guys I'm new in the Scala world and I want to create a simple REST API using Play Framework. I'm working on JSON mappings to Case Classes, and I have maybe a stupid question.
I have a case class that represents a UserDto (it is used for user registration, and getting some basic info about the user)
case class UserDto(id: Option[Int], email: String, username: String, password: Option[String], confirmedPassword: Option[String])
So as it says in the Play Framework docs, there are multiple ways to map this to JSON, and I'm looking at the automated mapping part
The way I've done it is
val userDtoWrites: OWrites[UserDto] = Json.writes[UserDto]
val userDtoReads: Reads[UserDto] = Json.reads[UserDto]
Now I want to add some validation to my UserDto, for example, to check if the Email is correct.
So my question is, is it somehow possible to create a Read for checking only the email field and add it to the automated generated Read?
I tried it with composeWith, but after running the second Read it only returns the email part.
val checkEmail: String = (JsPath \ "email").read[String](email)
val checkEmailJsString: JsString = checkEmail.map(s => JsString(s))
val newRead = userDtoReads.composeWith(checkEmailJsString))
For example, when a user wants to register:
{"email": "user#email.com", "username": "user","password": "password1234", "confirmedPassword": "password1234"}
That should map to:
UserDto(None, user#email.com, user, password1234, password1234)
if everything is correct. If the password does not match or the email has a bad format is should return 400 for example.
If you want to have instances of UserDto which only has valid email then you should consider using Refined types (using e.g. https://github.com/avdv/play-json-refined to provide support for Play JSON).
import eu.timepit.refined._
import eu.timepit.refined.auto._
import eu.timepit.refined.api._
import play.api.libs.json._
import de.cbley.refined.play.json._
type EmailPattern = "^\S+#\S+\.\S+$" // Scala 2.13 version
type EmailPattern = W.`"^\S+#\S+\.\S+$"`.T // before 2.13
type Email = String Refined string.MatchesRegex[EmailPattern]
case class UserDto(
id: Option[Int],
email: Email, // instead of plain String
username: String,
password: Option[String],
confirmedPassword: Option[String]
)
object UserDto {
implicit val userDtoWrites: OWrites[UserDto] = Json.writes[UserDto]
implicit val userDtoReads: Reads[UserDto] = Json.reads[UserDto]
}
If you don't want to use another library, you can implement your own Reads[UserDto]:
case class UserDto(id: Option[Int], email: String, username: String, password: Option[String], confirmedPassword: Option[String])
implicit val reads: Reads[UserDto] = (
(__ \ "id").readNullable[Int] and
(__ \ "email").read[String].filter(JsonValidationError("email is not valid"))(isValid) and
(__ \ "username").read[String] and
(__ \ "password").readNullable[String] and
(__ \ "confirmedPassword").readNullable[String]
) (UserDto).filter(
JsonValidationError("password and confirmedPassword must be equal")
) { userDto =>
userDto.password == userDto.confirmedPassword
}
Then you can use it:
val successfulJsonString = """{"email": "user#email.com", "username": "user", "password": "password1234", "confirmedPassword": "password1234"}"""
val emailJsonString = """{"email": "user", "username": "user","password": "password1234", "confirmedPassword": "password1234"}"""
val passwordsJsonString = """{"email": "user#email.com", "username": "user","password": "password1234", "confirmedPassword": "1234"}"""
Json.parse(successfulJsonString).validate[UserDto].fold(println, println)
Json.parse(emailJsonString).validateOpt[UserDto].fold(println, println)
Json.parse(passwordsJsonString).validate[UserDto].fold(println, println)
And get the output:
UserDto(None,user#email.com,user,Some(password1234),Some(password1234))
List((/email,List(JsonValidationError(List(email is not valid),List()))))
List((,List(JsonValidationError(List(field1 and field2 must be equal),List()))))
I took references from Scala play json combinators for validating equality and how to add custom ValidationError in Json Reads in PlayFramework to construct that solution.
Code run at Scastie.
Related
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
}
I have the following Json as var dataObject ={"files": ["code.R", "data.cv", "input.txt"]}.
I am posting this json as a body from the client side and I want to parse the Json and read these files names in the server side in play scala.
Please help
Because you have only one field, you can't use json combinators,
But you can do as follow:
case class Selection(files:List[String])
object Selection{
implicit val selectionReads = (__ \ 'files).read[List[String]].map{ l => Selection(l) }
implicit val selectionWrites = (__ \ 'files).write[List[String]].contramap { (selection: Selection) => selection.files}
//You can replace the above 2 lines with this line - depends on you.
implicit val selectionFormat: Format[Selection] = (__ \ 'files).format[List[String]].inmap(files => Selection(files), (selection: Selection) => selection.files)
}
Make sure you import:
import play.api.libs.functional.syntax._
This is the documentation: https://www.playframework.com/documentation/2.5.x/ScalaJson
And the solution is simple:
import play.api.libs.json._
val json: JsValue = Json.parse("{ "files": ["code.R","data.csv","input.txt"] }")
val files = (json \ "files").get
I am trying to parse a Travis-ci api response which has the following structure :
{
repos: [
{"id": ..., "slug": ...},
{"id": ..., "slug": ...},
{"id": ..., "slug": ...}
]
}
So I have decided to create case classes reflecting the json structure :
case class TravisRepository(id: String, slug: String)
case class TravisUserRepositories(repos: Seq[TravisRepository])
And I have added the implicit Read methods :
implicit val travisRepositoryReads: Reads[TravisRepository] = (
(JsPath \ "id").read[String] and
(JsPath \ "slug").read[String]
)(TravisRepository.apply _)
implicit val travisUserRepositoriesReads: Reads[TravisUserRepositories] = (
(JsPath \ "repos").read[Seq[TravisReposity]]
)(TravisUserRepositories.apply _)
However the second read is not compiling with the following error :
Overloaded method value [read] cannot be applied to (Seq[utils.TravisRepository] => utils.TravisUserRepositories)
When adding another column to the second Read, this compiles. With a single column, this is not compiling anymore. Can someone explain why is this not compiling? Is it a limitation of the Play-Json parser?
That's simply because you have the case "only one single field in your case class"...
To be able to use the Functional combining, you need at least 2 fields.
// would be OK implicit val travisUserRepositoriesReads:
Reads[TravisUserRepositories] = (
(JsPath \ "repos").read[Seq[TravisReposity]] and
... )(TravisUserRepositories.apply _)
// should be OK implicit val travisUserRepositoriesReads:
Reads[TravisUserRepositories] = (
(JsPath \ "repos").read[Seq[TravisReposity]] map (TravisUserRepositories.apply _)
Play 2.2.1, scala 2.10
// PersonModel.scala
case class PersonModel(name: String, age: Long)
object PersonModel2 {
implicit object PersonModelFormat extends Format[PersonModel] {
def reads(json: JsValue): PersonModel = PersonModel(
(json \ "name").as[String],
(json \ "age").as[Long])
def writes(u: PersonModel): JsValue = JsObject(List(
"name" -> JsString(u.name),
"age" -> JsNumber(u.age)))
}
sbt says
[error] PersonModel.scala:15: overriding method reads in trait Reads of type (json: play.api.libs.json.JsValue)play.api.libs.json.JsResult[models.PersonModel];
[error] method reads has incompatible type
[error] def reads(json: JsValue): PersonModel = PersonModel(
[error] ^
In this case, since you're not doing fancy things with the output json (like changing the key names in the resulting json object), I'd go for:
case class PersonModel(name: String, age: Long)
import play.api.libs.json._
implicit val personModelFormat = Json.format[PersonModel]
This way, you can, for example
scala> val j = PersonModel("julien", 35)
j: PersonModel = PersonModel(julien,35)
scala> println(Json.toJson(j))
{"name":"julien","age":35}
More info can be found here
HTH,
Julien
Things have changed in recent versions, I'd say for the better. In 2.2.x, you'd do it this way, using the new functional syntax and combinators:
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val PersonModelFormat: Format[PersonModel] = (
(__ \ "name").format[String] and
(__ \ "age").format[Long]
)(PersonModel.apply, unlift(PersonModel.unapply))
Much shorter!
The documentation for 2.2.x http://www.playframework.com/documentation/2.2.1/ScalaJsonCombinators provides a good explanation for the rationale for the change.
For single usage there is an inline solution:
Json.writes[PersonModel].writes(personModelInstance) // returns JsObject
From documentation:
macro-compiler replaces Json.writes[User] by injecting into compile chain the exact code you would write yourself
I am experimenting with the json4s library (based on lift-json). One of the things I would like to do is to parse a JSON string into an AST, and then manipulate it.
For example, I would like to upsert a field (insert the field into the AST if it does not exist, or update its value if it does).
I have not been able to find how to do it in the documentation. Experimenting with the available methods, I have come up with the following, which works, but feels clumsy.
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.native.JsonMethods._
object TestJson {
implicit val formats = DefaultFormats
def main(args: Array[String]): Unit = {
val json = """{"foo":1, "bar":{"foo":2}}"""
val ast = parse(json).asInstanceOf[JObject]
println( upsertField(ast, ("foo" -> "3")) )
println( upsertField(ast, ("foobar" -> "3")) )
}
def upsertField(src:JObject, fld:JField): JValue = {
if(src \ fld._1 == JNothing){
src ~ fld
}
else{
src.replace(List(fld._1), fld._2)
}
}
}
I dislike it for many reasons:
Having to explicitly cast the results of parse(json) to JObject
The result of the upsertField function is a JValue, which I will have to recast if I want to manipulate the object further
The upsertField function just feels very unelegant
It does not work for fields that are not at the top level of the hierarchy
Is there a better way to transform the AST?
EDIT: as a workaround to the problem, I have managed to convert my JSON to Scala regular classes, and manipulate them with lenses (Using Lenses on Scala Regular Classes)
There is the merge function which creates or overrides a field. You can also update fields that are not at the root level of the tree.
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
object mergeJson extends App {
val json =
"""
|{
| "foo":1,
| "bar": {
| "foo": 2
| }
|}
|""".stripMargin
val ast = parse(json)
val updated = ast merge (("foo", 3) ~ ("bar", ("fnord", 5)))
println(pretty(updated))
// {
// "foo" : 3,
// "bar" : {
// "foo" : 2,
// "fnord" : 5
// }
// }
}
Let me also give you the SON of JSON version:
import nl.typeset.sonofjson._
val json = parse("""{ "foo" : 1, "bar" : { "foo" : 2 } }""")
// or, perhaps a little easier
val json = obj(foo = 1, bar = obj(foo = 2))
json.foo = "3"
json.foobar = "3"
When I was implementing some very specific json diff using lift json I used a lot of recursive functions to get to the jpath where I need to modify value, and modified json was constructed when recursion "collapsed". LiftJson is immutable after all. You mentioned lenses as another approach, which is very interesting by itself. But my current favourite is play-json library that is working like a charm in a situation when you need to do json-to-json transformation:
from Mandubian Blog:
val gizmo2gremlin = (
(__ \ 'name).json.put(JsString("gremlin")) and
(__ \ 'description).json.pickBranch(
(__ \ 'size).json.update( of[JsNumber].map{ case JsNumber(size) => JsNumber(size * 3) } ) and
(__ \ 'features).json.put( Json.arr("skinny", "ugly", "evil") ) and
(__ \ 'danger).json.put(JsString("always"))
reduce
) and
(__ \ 'hates).json.copyFrom( (__ \ 'loves).json.pick )
) reduce
Yummy Features: all transformers are combinators that can be mixed together, validation, shapeless support, auto marshaling of case classes with implicit overrides, stand-alone library.