Scala Object From JSON Request - json

I have a controller method in my Scala Play project that takes JSON as input. I would like to turn this JSON into a model object I have.
Here is my controller method:
def broadcastPost = Action(parse.json) { request =>
(request.body).asOpt[Post].map { post =>
Post.create(post.channelId, post.message, post.datePosted, post.author)
Ok(play.api.libs.json.Json.toJson(
Map("status" -> "OK", "message" -> ("Post created"))
))
}.getOrElse {
BadRequest(play.api.libs.json.Json.toJson(
Map("status" -> "Error", "message" -> ("Missing parameter [Post]"))
))
}
}
And here is the model:
case class Post(id: Pk[Long], channelId: Long, message: String, datePosted: Date, author: String)
and its implicit formatter:
implicit val postFormat = (
(__ \ "id").formatNullable[Long] and
(__ \ "channelId").format[Long] and
(__ \ "message").format[String] and
(__ \ "datePosted").format[Date] and
(__ \ "author").format[String]
)((id, channelId, message, datePosted, author) => Post(id.map(Id(_)).getOrElse(NotAssigned), channelId, message, datePosted, author),
(p: Post) => (p.id.toOption, p.channelId, p.message, p.datePosted, p.author))
When I send a POST request to that method with the following data:
{"channelId":1, "message":"Wanna get a game in?", "dateCreated":"5-15-2013", "author":"Eliot Fowler"}
I get the following response:
{"status":"Error","message":"Missing parameter [Post]"}
I am very new to Scala, so I may be overlooking something very simple here.

Instead of using asOpt, which loses the error, you should use validate, which will allow you to return the error message, and then see what the problem is, eg:
def broadcastPost = Action(parse.json) { request =>
request.body.validate[Post].fold({ errors =>
BadRequest(Json.obj(
"status" -> "Error",
"message" -> "Bad JSON",
"details" -> JsError.toFlatJson(errors)
))
}, { post =>
Post.create(post.channelId, post.message, post.datePosted, post.author)
Ok(Json.obj("status" -> "OK", "message" -> "Post created"))
})
}
Now, what I'm guessing that will tell you is that "5-15-2013" is not a valid date. The default date format for JSON in Play is yyyy-MM-dd. You can specify a custom one by modifying your format to say:
...
(__ \ "datePosted").format[Date](Format(Reads.dateReads("MM-dd-yyyy"), Writes.dateWrites("MM-dd-yyyy"))) and
...
Another built in one Reads.IsoDateReads, this is more standard than the American only month day year format, another approach that avoids this issue altogether is use a Long as milliseconds since epoch.

Related

how to add custom ValidationError in Json Reads in PlayFramework

I am using play Reads validation helpers i want to show some custom message in case of json exception eg:length is minimum then specified or the given email is not valid , i knnow play displays the error message like this error.minLength but i want to display a reasonable message like please enter the character greater then 1 (or something ) here is my code
case class DirectUserSignUpValidation(firstName: String,
lastName: String,
email: String,
password: String) extends Serializable
object DirectUserSignUpValidation {
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).
filterNot(ValidationError("Password is all numbers"))(_.forall(_.isDigit)).
filterNot(ValidationError("Password is all letters"))(_.forall(_.isLetter))
)) (UserSignUpValidation.apply _)
}
i have tried to add ValidationErrorlike this
(JsPath \ "email").read(email,Seq(ValidationError("email address not correct")) and
but its giving me compile time error
too many arguments for method read: (t: T)play.api.libs.json.Reads[T]
please helo how can i add custom validationError messages while reading json data
There is no such thing as (JsPath \ "firstName").read(minLength[String](1)) in play json. what you can do with custom error message is this:
(JsPath \ "firstName")
.read[String]
.filter(ValidationError("your.error.message"))(_.length > 0)
ValidationError messages are supposed to be keys to be used for translation, not human readable messages.
However, if you still want to change the message for minLength, you'll need to reimplement it, since it is hard-coded.
Thankfully, the source code is available, so you can easily change it as you please:
def minLength[M](m: Int)(implicit reads: Reads[M], p: M => scala.collection.TraversableLike[_, M]) =
filterNot[M](JsonValidationError("error.minLength", m))(_.size < m)
If you want to use a more generic pattern to specify errors, the only access you have is using the result from your validation. For instance, you could do
val json: JsValue = ???
json.validate[DirectUserSignUpValidation] match {
case JsSuccess(dusuv, _) => doSomethingWith(dusuv)
case JsError(errs) => doSomethingWithErrors(errs)
}
Or, with a more compact approach
json.validate[DirectUserSignUpValidation].
fold(doSomethingWithErrors, doSomethingWith)

Play JSON: Reading and validating a JsObject with unknown keys

I'm reading a nested JSON document using several Reads[T] implementations, however, I'm stuck with the following sub-object:
{
...,
"attributes": {
"keyA": [1.68, 5.47, 3.57],
"KeyB": [true],
"keyC": ["Lorem", "Ipsum"]
},
...
}
The keys ("keyA", "keyB"...) as well as the amount of keys are not known at compile time and can vary. The values of the keys are always JsArray instances, but of different size and type (however, all elements of a particular array must have the same JsValue type).
The Scala representation of one single attribute:
case class Attribute[A](name: String, values: Seq[A])
// 'A' can only be String, Boolean or Double
The goal is to create a Reads[Seq[Attribute]] that can be used for the "attributes"-field when transforming the whole document (remember, "attributes" is just a sub-document).
Then there is a simple map that contains allowed combinations of keys and array types that should be used to validate attributes. Edit: This map is specific for each request (or rather specific for every type of json document). But you can assume that it is always available in the scope.
val required = Map(
"KeyA" -> "Double",
"KeyB" -> "String",
"KeyD" -> "String",
)
So in the case of the JSON shown above, the Reads should create two errors:
"keyB" does exist, but has the wrong type (expected String, was boolean).
"keyD" is missing (whereas keyC is not needed and can be ignored).
I'm having trouble creating the necessary Reads. The first thing I tried as a first step, from the perspective of the outer Reads:
...
(__ \ "attributes").reads[Map[String, JsArray]]...
...
I thought this is a nice first step because if the JSON structure is not an object containing Strings and JsArrays as key-value pairs, then the Reads fails with proper error messages. It works, but: I don't know how to go on from there. Of course I just could create a method that transforms the Map into a Seq[Attribute], but this method somehow should return a JsResult, since there are further validations to do.
The second thing I tried:
val attributeSeqReads = new Reads[Seq[Attribute]] {
def reads(json: JsValue) = json match {
case JsObject(fields) => processAttributes(fields)
case _ => JsError("attributes not an object")
}
def processAttributes(fields: Map[String, JsValue]): JsResult[Seq[Attribute]] = {
// ...
}
}
The idea was to validate each element of the map manually within processAttributes. But I think this is too complicated. Any help is appreciated.
edit for clarification:
At the beginning of the post I said that the keys (keyA, keyB...) are unknown at compile time. Later on I said that those keys are part of the map required which is used for validation. This sounds like a contradiction, but the thing is: required is specific for each document/request and is also not known at compile time. But you don't need to worry about that, just assume that for every request the correct required is already available in the scope.
You are too confused by the task
The keys ("keyA", "keyB"...) as well as the amount of keys are not known at compile time and can vary
So the number of keys and their types are known in advance and the final?
So in the case of the JSON shown above, the Reads should create two
errors:
"keyB" does exist, but has the wrong type (expected String, was
boolean).
"keyD" is missing (whereas keyC is not needed and can be ignored).
Your main task is just to check the availability and compliance?
You may implement Reads[Attribute] for every your key with Reads.list(Reads.of[A]) (this Reads will check type and required) and skip omitted (if not required) with Reads.pure(Attribute[A]). Then tuple convert to list (_.productIterator.toList) and you will get Seq[Attribute]
val r = (
(__ \ "attributes" \ "keyA").read[Attribute[Double]](list(of[Double]).map(Attribute("keyA", _))) and
(__ \ "attributes" \ "keyB").read[Attribute[Boolean]](list(of[Boolean]).map(Attribute("keyB", _))) and
((__ \ "attributes" \ "keyC").read[Attribute[String]](list(of[String]).map(Attribute("keyC", _))) or Reads.pure(Attribute[String]("keyC", List()))) and
(__ \ "attributes" \ "keyD").read[Attribute[String]](list(of[String]).map(Attribute("keyD", _)))
).tupled.map(_.productIterator.toList)
scala>json1: play.api.libs.json.JsValue = {"attributes":{"keyA":[1.68,5.47,3.57],"keyB":[true],"keyD":["Lorem","Ipsum"]}}
scala>res37: play.api.libs.json.JsResult[List[Any]] = JsSuccess(List(Attribute(keyA,List(1.68, 5.47, 3.57)), Attribute(KeyB,List(true)), Attribute(keyC,List()), Attribute(KeyD,List(Lorem, Ipsum))),)
scala>json2: play.api.libs.json.JsValue = {"attributes":{"keyA":[1.68,5.47,3.57],"keyB":[true],"keyC":["Lorem","Ipsum"]}}
scala>res38: play.api.libs.json.JsResult[List[Any]] = JsError(List((/attributes/keyD,List(ValidationError(List(error.path.missing),WrappedArray())))))
scala>json3: play.api.libs.json.JsValue = {"attributes":{"keyA":[1.68,5.47,3.57],"keyB":["Lorem"],"keyC":["Lorem","Ipsum"]}}
scala>res42: play.api.libs.json.JsResult[List[Any]] = JsError(List((/attributes/keyD,List(ValidationError(List(error.path.missing),WrappedArray()))), (/attributes/keyB(0),List(ValidationError(List(error.expected.jsboolean),WrappedArray())))))
If you will have more than 22 attributes, you will have another problem: Tuple with more than 22 properties.
for dynamic properties in runtime
inspired by 'Reads.traversableReads[F[_], A]'
def attributesReads(required: Map[String, String]) = Reads {json =>
type Errors = Seq[(JsPath, Seq[ValidationError])]
def locate(e: Errors, idx: Int) = e.map { case (p, valerr) => (JsPath(idx)) ++ p -> valerr }
required.map{
case (key, "Double") => (__ \ key).read[Attribute[Double]](list(of[Double]).map(Attribute(key, _))).reads(json)
case (key, "String") => (__ \ key).read[Attribute[String]](list(of[String]).map(Attribute(key, _))).reads(json)
case (key, "Boolean") => (__ \ key).read[Attribute[Boolean]](list(of[Boolean]).map(Attribute(key, _))).reads(json)
case _ => JsError("")
}.iterator.zipWithIndex.foldLeft(Right(Vector.empty): Either[Errors, Vector[Attribute[_ >: Double with String with Boolean]]]) {
case (Right(vs), (JsSuccess(v, _), _)) => Right(vs :+ v)
case (Right(_), (JsError(e), idx)) => Left(locate(e, idx))
case (Left(e), (_: JsSuccess[_], _)) => Left(e)
case (Left(e1), (JsError(e2), idx)) => Left(e1 ++ locate(e2, idx))
}
.fold(JsError.apply, { res =>
JsSuccess(res.toList)
})
}
(__ \ "attributes").read(attributesReads(Map("keyA" -> "Double"))).reads(json)
scala> json: play.api.libs.json.JsValue = {"attributes":{"keyA":[1.68,5.47,3.57],"keyB":[true],"keyD":["Lorem","Ipsum"]}}
scala> res0: play.api.libs.json.JsResult[List[Attribute[_ >: Double with String with Boolean]]] = JsSuccess(List(Attribute(keyA,List(1.68, 5.47, 3.57))),/attributes)

Simple CRUD with Scala and Playframework

Technology stack: Scala, PlayFramework 2.3.8, MongoDB ( Salat + casbah ).
I am developing some restful APIs, with simple CRUD.
The entity:
case class Company(
_id: ObjectId = new ObjectId,
user_id: Any = "",
cinema_id: Int = 0,
name: String = "",
status: String = "",
releaseId: Int, fileId: Int,
date: CompanyDate,
showTime: List[Int] = List())
case class CompanyDate(start: DateTime, end: DateTime)
I create Reads for creating the object from JSON:
def createFromJson(json: JsValue, user: User): Reads[Company] = {
val user_id = (for {
cinema_id <- (json \ "cinema_id").asOpt[Int]
cinema <- user.cinema.find(_.id == cinema_id)
} yield cinema.getUser(user._id.toString)
).getOrElse(0)
(
Reads.pure(new ObjectId) and
Reads.pure[Any](user_id).filter(ValidationError("You have no access to this cinema"))(_ != 0) and
(__ \ "cinema_id").read[Int] and
(__ \ "name").read[String](minLength[String](1) keepAnd maxLength[String](100)) and
Reads.pure[String]("new") and
(__ \ "release_id").read[Int] and
(__ \ "file_id").read[Int] and
(__ \ "date").read[CompanyDate] and
(__ \ "show_time").read[List[Int]](minLength[List[Int]](1))
)(Company.apply _).flatMap(company => Reads { _ =>
if (company.date.start isAfter company.date.end) {
JsError(JsPath \ "date" \ "start" -> ValidationError("Start < end"))
} else {
JsSuccess(company)
}
})
}
It took me create wrapped function to pass user to Reads, because some field ( user_id ) calculate from current user - client doesn't know this information.
It looks a little ugly, but works ok.
The Controller for create action is very simple, it takes json, casts it to class and saves to DB.
Next thing: Update. In update method i want reuse some validation cases ( check date.start < date.end, e.g. ), but i don't receive the full object. My client only sends me changed fields.
Some fields ( cinema_id and user_id ) can only be set when creating object, they can't be updated.
Example request (UPDATED):
PUT /api/company/1
{
"name": "Name",
"status": "delete"
}
How can i validate this json and update existing object?
What is the best approach to solve this task?

How to use different names when mapping JSON array to Scala object using combinators

Given a JSON array like this one:
{
"success": true,
"data": [
{
"id": 600,
"title": "test deal",
"e54cbe3a434d8e6": 54
},
{
"id": 600,
"title": "test deal",
"e54cbe3a434d8e6": 54
},
],
"additional_data": {
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": false
}
}
}
In my Play 2.2.2 application, using the Scala JSON Reads Combinator, everything works going this way:
implicit val entityReader = Json.reads[Entity]
val futureJson: Future[List[Entity]] = futureResponse.map(
response => (response.json \ "data").validate[List[Entity]].get
The problem now is the key named 'e54cbe3a434d8e6' which I would like to name 'value' in my object:
// This doesn't work, as one might expect
case class Entity(id: Long, title: String, e54cbe3a434d8e6: Long)
// I would like to use 'value' instead of 'e54cbe3a434d8e6'
case class Entity(id: Long, title: String, value: Long)
There is vast information about the combinators here and here but I only want to use a fieldname which is different from the key name in the JSON array. Can some one help me to find a simple way?
I suppose it has got something to do with JSON.writes?!
One simple way without trying to apply transformations on json itself is to define a custom Reads in such a way to handle this:
val json = obj(
"data" -> obj(
"id" -> 600,
"title" -> "test deal",
"e54cbe3a434d8e6" -> 54))
case class Data(id: Long, title: String, value: Int)
val reads = (
(__ \ "id").read[Long] ~
(__ \ "title").read[String] ~
(__ \ "e54cbe3a434d8e6").read[Int] // here you get mapping from your json to Scala case class
)(Data)
def index = Action {
val res = (json \ "data").validate(reads)
println(res) // prints "JsSuccess(Data(600,test deal,54),)"
Ok(json)
}
Another way is to use combinators like this:
... the same json and case class
implicit val generatedReads = reads[Data]
def index = Action {
val res = (json \ "data").validate(
// here we pick value at 'e54cbe3a434d8e6' and put into brand new 'value' branch
__.json.update((__ \ "value").json.copyFrom((__ \ "e54cbe3a434d8e6").json.pick)) andThen
// here we remove 'e54cbe3a434d8e6' branch
(__ \ "e54cbe3a434d8e6").json.prune andThen
// here we validate result with generated reads for our case class
generatedReads)
println(res) // prints "JsSuccess(Data(600,test deal,54),/e54cbe3a434d8e6/e54cbe3a434d8e6)"
Ok(prettyPrint(json))
}

JSON List to Scala List

I am creating a backend API with Play Framework and Scala. I would like to map the incoming request to a scala object. One of the instance variables of the object is a list of channels. Here is what I currently have:
Controller method that takes the request and attempts to map it to a user:
def addUser = Action(parse.json) { request =>
request.body.validate[User].fold({ errors =>
BadRequest(Json.obj(
"status" -> "Error",
"message" -> "Bad JSON",
"details" -> JsError.toFlatJson(errors)
))
}, { user =>
User.create(user.pushToken, user.channels)
Ok(Json.obj("status" -> "OK", "message" -> "User created"))
})
}
User case class:
case class User(id: Pk[Long], pushToken: String, channels: List[String])
User formatter:
implicit val userFormat = (
(__ \ "id").formatNullable[Long] and
(__ \ "pushToken").format[String] and
(__ \ "channels").format[List[String]]
)((id, pushToken, channels) => User(id.map(Id(_)).getOrElse(NotAssigned), pushToken, channels),
(u: User) => (u.id.toOption, u.pushToken, u.channels))
User anorm create method:
def create(pushToken: String, channels: List[String]) {
DB.withConnection { implicit c =>
SQL("insert into user (pushToken, channels) values ({pushToken}, {channels})").on(
'pushToken -> pushToken,
'channels -> channels
).executeUpdate()
}
}
When I try to compile, I get:
Compilation error[could not find implicit value for parameter extractor: anorm.Column[List[String]]]
Ideally, I would like to be able to accept this as a user:
{
"pushToken":"4jkf-fdsja93-fjdska34",
"channels": [
"channelA", "channelB", "channelC"
]
}
and create a user from it.
You can't use List[String] as column value in Anorm, thats the problem
You should use mkString method or smth else