JSON List to Scala List - json

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

Related

Play JSON Reads[T]: split a JsArray into multiple subsets

I have a JSON structure that contains an array of events. The array is "polymorphic" in the sense that there are three possible event types A, B and C:
{
...
"events": [
{ "eventType": "A", ...},
{ "eventType": "B", ...},
{ "eventType": "C", ...},
...
]
}
The three event types don't have the same object structure, so I need different Reads for them. And apart from that, the target case class of the whole JSON document distinguishes between the events:
case class Doc(
...,
aEvents: Seq[EventA],
bEvents: Seq[EventB],
cEvents: Seq[EventC],
...
)
How can I define the internals of Reads[Doc] so that the json array events is split into three subsets which are mapped to aEvents, bEvents and cEvents?
What I tried so far (without being succesful):
First, I defined a Reads[JsArray] to transform the original JsArray to another JsArray that only contains events of a particular type:
def eventReads(eventTypeName: String) = new Reads[JsArray] {
override def reads(json: JsValue): JsResult[JsArray] = json match {
case JsArray(seq) =>
val filtered = seq.filter { jsVal =>
(jsVal \ "eventType").asOpt[String].contains(eventTypeName)
}
JsSuccess(JsArray(filtered))
case _ => JsError("Must be an array")
}
}
Then the idea is to use it like this within Reads[Doc]:
implicit val docReads: Reads[Doc] = (
...
(__ \ "events").read[JsArray](eventReads("A")).andThen... and
(__ \ "events").read[JsArray](eventReads("B")).andThen... and
(__ \ "events").read[JsArray](eventReads("C")).andThen... and
...
)(Doc.apply _)
However, I don't know how to go on from here. I assume the andThen part should look something like this (in case of event a):
.andThen[Seq[EventA]](EventA.reads)
But that doesn't work since I expect the API to create a Seq[EventA] by explicitly passing a Reads[EventA] instead of Reads[Seq[EventA]]. And apart from that, since I've never got it running, I'm not sure if this whole approach is reasonable in the first place.
edit: in case the original JsArray contains unknown event types (e.g. D and E), these types should be ignored and left out from the final result (instead of making the whole Reads fail).
put implicit read for every Event type like
def eventRead[A](et: String, er: Reads[A]) = (__ \ "eventType").read[String].filter(_ == et).andKeep(er)
implicit val eventARead = eventRead("A", Json.reads[EventA])
implicit val eventBRead = eventRead("B", Json.reads[EventB])
implicit val eventCRead = eventRead("C", Json.reads[EventC])
and use Reads[Doc] (folding event list to separate sequences by types and apply result to Doc):
Reads[Doc] = (__ \ "events").read[List[JsValue]].map(
_.foldLeft[JsResult[ (Seq[EventA], Seq[EventB], Seq[EventC]) ]]( JsSuccess( (Seq.empty[EventA], Seq.empty[EventB], Seq.empty[EventC]) ) ){
case (JsSuccess(a, _), v) =>
(v.validate[EventA].map(e => a.copy(_1 = e +: a._1)) or v.validate[EventB].map(e => a.copy(_2 = e +: a._2)) or v.validate[EventC].map(e => a.copy(_3 = e +: a._3)))
case (e, _) => e
}
).flatMap(p => Reads[Doc]{js => p.map(Doc.tupled)})
it will create Doc in one pass through events list
JsSuccess(Doc(List(EventA(a)),List(EventB(b2), EventB(b1)),List(EventC(c))),)
the source data
val json = Json.parse("""{"events": [
| { "eventType": "A", "e": "a"},
| { "eventType": "B", "ev": "b1"},
| { "eventType": "C", "event": "c"},
| { "eventType": "B", "ev": "b2"}
| ]
|}
|""")
case class EventA(e: String)
case class EventB(ev: String)
case class EventC(event: String)
I would model the fact that you store different event types in your JS array as a class hierarchy to keep it type safe.
sealed abstract class Event
case class EventA() extends Event
case class EventB() extends Event
case class EventC() extends Event
Then you can store all your events in a single collection and use pattern matching later to refine them. For example:
case class Doc(events: Seq[Event]) {
def getEventsA: Seq[EventA] = events.flatMap(_ match {
case e: EventA => Some(e)
case _ => None
})
}
Doc(Seq(EventA(), EventB(), EventC())).getEventsA // res0: Seq[EventA] = List(EventA())
For implementing your Reads, Doc will be naturally mapped to the case class, you only need to provide a mapping for Event. Here is what it could look like:
implicit val eventReads = new Reads[Event] {
override def reads(json: JsValue): JsResult[Event] = json \ "eventType" match {
case JsDefined(JsString("A")) => JsSuccess(EventA())
case JsDefined(JsString("B")) => JsSuccess(EventB())
case JsDefined(JsString("C")) => JsSuccess(EventC())
case _ => JsError("???")
}
}
implicit val docReads = Json.reads[Doc]
You can then use it like this:
val jsValue = Json.parse("""
{
"events": [
{ "eventType": "A"},
{ "eventType": "B"},
{ "eventType": "C"}
]
}
""")
val docJsResults = docReads.reads(jsValue) // docJsResults: play.api.libs.json.JsResult[Doc] = JsSuccess(Doc(List(EventA(), EventB(), EventC())),/events)
docJsResults.get.events.length // res1: Int = 3
docJsResults.get.getEventsA // res2: Seq[EventA] = List(EventA())
Hope this helps.

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 send Json from client with missing fields for its corresponding Case Class after using Json.format function

I have a case Class and its companion object like below. Now, when I send JSON without id, createdAt and deletedAt fields, because I set them elsewhere, I get [NoSuchElementException: JsError.get] error. It's because I do not set above properties.
How could I achieve this and avoid getting the error?
case class Plan(id: String,
companyId: String,
name: String,
status: Boolean = true,
#EnumAs planType: PlanType.Value,
brochureId: Option[UUID],
lifePolicy: Seq[LifePolicy] = Nil,
createdAt: DateTime,
updatedAt: DateTime,
deletedAt: Option[DateTime]
)
object Plan {
implicit val planFormat = Json.format[Plan]
def fromJson(str: JsValue): Plan = Json.fromJson[Plan](str).get
def toJson(plan: Plan): JsValue = Json.toJson(plan)
def toJsonSeq(plan: Seq[Plan]): JsValue = Json.toJson(plan)
}
JSON I send from client
{
"companyId": "e8c67345-7f59-466d-a958-7c722ad0dcb7",
"name": "Creating First Plan with enum Content",
"status": true,
"planType": "Health",
"lifePolicy": []
}
You can introduce another case class just to handle serialization from request:
like this
case class NewPlan(name: String,
status: Boolean = true,
#EnumAs planType: PlanType.Value,
brochureId: Option[UUID],
lifePolicy: Seq[LifePolicy] = Nil
)
and then use this class to populate your Plan class.
The fundamental issue is that by the time a case class is instantiated to represent your data, it must be well-typed. To shoe horn your example data into your example class, the types don't match because some fields are missing. It's literally trying to call the constructor without enough arguments.
You've got a couple options:
You can make a model that represents the incomplete data (as grotrianster suggested).
You can make the possible missing fields Option types.
You can custom-write the Reads part of your Format to introduce intelligent values or dummy values for the missing ones.
Option 3 might look something like:
// Untested for compilation, might need some corrections
val now: DateTime = ...
val autoId = Reads[JsObject] {
case obj: JsObject => JsSuccess(obj \ 'id match {
case JsString(_) => obj
case _ => obj.transform(
__.update((__ \ 'id).json.put("")) andThen
__.update((__ \ 'createdTime).json.put(now)) andThen
__.update((__ \ 'updatedTime).json.put(now))
)
})
case _ => JsError("JsObject expected")
}
implicit val planFormat = Format[Plan](
autoId andThen Json.reads[Plan],
Json.writes[Plan])
Once you do this once, if the issue is the same for all your other models, you can probably abstract it into some Format factory utility function.
This may be slightly cleaner for autoId:
val autoId = Reads[JsObject] {
// Leave it alone if we have an ID already
case obj: JsObject if (obj \ 'id).asOpt[String].isSome => JsSuccess(obj)
// Insert dummy values if we don't have an `id`
case obj: JsObject => JsSuccess(obj.transform(
__.update((__ \ 'id).json.put("")) andThen
__.update((__ \ 'createdTime).json.put(now)) andThen
__.update((__ \ 'updatedTime).json.put(now))
))
case _ => JsError("JsObject expected")
}

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

Scala Object From JSON Request

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.