Here is a beginner question :
I have defined Event like this :
case class Event(id: Pk[Long], name: String, userId: Pk[Long])
object Event {
private val EventParser: RowParser[Event] = {
get[Pk[Long]]("id") ~
get[String]("name") ~
get[Pk[Long]]("userId") map {
case id ~ name ~ userId => Event(id, name, userId)
}
}
def findAll(): Seq[Event] = {
DB.withConnection { implicit connection =>
SQL("select * from events").as(EventParser *)
}
}
}
And I render it to the view like this :
def events = Action {
val events: Seq[Event] = Event.findAll()
Ok(views.html.events(events))
}
But I would like to return Json data.
Json.toJson(events) can't be used since events type is Seq.
I didn't find a good tutorial on the subject and I tried to follow this answer : play framework working with json objects in Scala but it doesn't seem to work with play 2.2.
So my question is : Do you know an easy way to render a sequence in Json to the view after accessing database?
Try this:
import play.api.libs.json._
object Event {
...
implicit def pkWrites[T : Writes]: Writes[Pk[T]] = Writes {
case anorm.Id(t) => implicitly[Writes[T]].writes(t)
case anorm.NotAssigned => JsNull
}
implicit val eventWrites = Json.writes[Event]
}
Related
I would like to know that how can I return json response data from Play(2.2.x) Scala controller class to display on my view page ? I have json objects in Postgresql database(table name: "test" and having: id and name). Please provide me any solutions for it.
I have tried the following cases(a and b), but I am not sure why I am not getting the response(like: names) on my controller, so I can show them on my view page ? since I am very new to Play/Scala and Postgresql.
case a. If I give like:
model:
def getTestValuesFromTable() = {
DB.withConnection { implicit connection =>
val selectJson =SQL("select name from test").on().apply().collect {
case Row(id:Long, Some(name:String)) =>
new TestContent(name)
}
//.head
//JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)
}
}
controller:
def getTest = Action {
val response = TestContent.getTestValuesFromTable()
Ok("Done")
//Ok(response)
}
Output is: Done(application is executing fine without any exceptions, of course json data is not coming since I am returning: Done only, so getting output: "Done")
case b. If I do like this: getting error: not enough arguments for method apply: (n: Int)models.Testname in trait LinearSeqOptimized. Unspecified value parameter n. I really not sure how can I get my response for it ?
controller:
def getTest = Action {
val response = TestContent.getTestValuesFromTable()
// Ok("Done")
Ok(response)
}
model:
def getTestValuesFromTable(): JsValue = {
DB.withConnection { implicit connection =>
val selectJson = SQL("select * from test")
JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)
// val selectJson =SQL("select name from test").on().apply().collect {
// case Row(id:Long, Some(name:String)) =>
// new TestContent(name)
// }
//.head
JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)//not enough arguments for method apply: (n: Int)models.Testname in trait LinearSeqOptimized. Unspecified value parameter n.
}
}
Please let me know how to get my response ?
getJsonValuesFromTable method return nothing (Unit). To fix it change definition of this method to
def getJsonValuesFromTable(testContent: TestContent) = {
or explicitly setting type:
def getJsonValuesFromTable(testContent: TestContent): Unit = {
Also as a next step to let client know that you are returning json, you should set content type:
Ok(Json.obj(response)).as("application/json")
I can process this json when all the inputs are valid, i.e with valid keys (including case) and values. The next step is to validate keys and return 400 (Bad Request) if the keys or values are invalid. What is a good way to add this validation?
The API call
POST http://localhost:8080/api/v1/adsession
Content-Type: application/json
body {
"sessionId": "abcd123123123",
"serviceGroup": "1234",
"targetCode": {"zipcodes":"30096,30188","code2":"value2"}
}
Route handler
class AdSessionRoutes(services: Services)(implicit ec: ExecutionContext, log: LoggingContext) extends ApiRoute(services) {
implicit val timeout = Timeout(10 seconds)
val postSession = pathPrefix("adsession") & pathEnd & post
val route: Route = {
withService("adSession") { service =>
postSession {
entity(as[AdSession]) { adSession =>
log.info(s"Processing POST ${adSession}")
val future = (service ? CreateAdSession(adSession)).mapTo[AdSession]
onComplete(future) {
case Success(result) =>
complete(StatusCodes.Created, result)
case Failure(e) =>
log.error(s"Error: ${e.toString}")
complete(StatusCodes.InternalServerError, Message(ApiMessages.UnknownException))
}
}
}
}
}
}
Model object
case class AdSession(
sessionId: String,
serviceGroup: String,
targetCodes: Map[String,String],
id: Option[String] = None)
object AdSessionJsonProtocol extends DefaultJsonProtocol {
implicit val adSessionFormat = jsonFormat4(AdSession)
}
entity(as[AdSession]) does map keys to case class fields, but I'm not sure how to catch those errors. I would like to capture those errors as well as add additional validations and return 400 with valid json error response.
I know this may be a little late but since akka-http-2.4.6, you can put the verification logic inside a case class. Check out this for info on how to do it.
Define your own read and write methods for AdSession like this:
object AdSessionJsonProtocol {
implicit object adSessionJsonFormat extends RootJsonFormat[AdSession] {
override def read(json: JsValue): AdSession = ???
override def write(obj: AdSession): JsValue = ???
}
}
Inside read function you can perform additional validation.
Another question is how to pass collected errors to Spray route. I would like to suggest you to wrap AdSession into Either[String, AdSession], for example:
postSession {
entity(as[Either[String, AdSession]]) { adSession =>
So, now your adSessionJsonFormat will looks like:
implicit object adSessionJsonFormat extends RootJsonFormat[Either[String, AdSession]] {
override def read(json: JsValue): Either[String, AdSession] = {
// json.asJsObject.fields access fields, perform checks
// if everything is OK return Right(AdSession(...))
// otherwise return Lift("Error Message")
}
override def write(obj: Either[String, AdSession]): JsValue = ???
}
But, I think it's possible to solve it in more elegant way using some implicit magic.
I'm using spray with json4s, and I've got the implementation below to handle put requests for updating objects... My problem with it, is that I first extract an instance of SomeObject from the json, but being a RESTful api, I want the ID to be specified in the URL. So then I must somehow create another instance of SomeObject that is indexed with the ID... to do this, I'm using a constructor like SomeObject(id: Long, obj: SomeObject). It works well enough, but the implementation is ugly and it feels inefficient. What can I do so I can somehow stick the ID in there so that I'm only creating one instance of SomeObject?
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObject]
val extractedObjectWithId = SomeObject(id, extractedObject)
handleRequest(extractedObjectWithId)
}
}
}
}
case class SomeObject(id: Long, data: String, someValue: Double, someDate: DateTime) {
def this(data: String, someValue: Double, someDate: DateTime) = this(0, data, someValue, someDate)
def this(id: Long, obj: SomeObject) = this(id, obj.data, obj.someValue, obj.someDate)
}
I figured out a solution to this after digging around through the docs for awhile:
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.merge {
("id", id)
}.extract[SomeObject]
handleRequest(extractedObject)
}
}
}
}
Since id field is not set on all instances, it means it is optional so use Option type to indicate it. Define your case class with id: Option[Long] field. This make json parser to skip id field when it is absent but allow you to assign a value when you have.
case class SomeObject(id: Option[Long], data: String, someValue: Double, someDate: DateTime)
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObject]
val extractedObjectWithId = extractedObject.copy(id = Some(id))
handleRequest(extractedObjectWithId)
}
}
}
}
And don't worry about performance impacts of creating new objects. It may affect performance much less than you thought. You should measure performance before improving it.
I do not know about efficiency, but you can make your code "less ugly" by defining a SomeObjectBuilder, to which you extract your JSON value.
case class SomeObjectBuilder(data: String, someValue: Double, someDate: DateTime) {
def setId(id: Long) = SomeObject(id, data, someValue, someDate)
}
case class SomeObject(id: Long, data: String, someValue: Double, someDate: DateTime)
With the extraction:
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObjectBuilder]
val extractedObjectWithId = extractedObject.setId(id)
handleRequest(extractedObjectWithId)
}
}
}
}
This way, you are not using a default id set to zero, which is, if I understand correctly, never correct. The only reason you're setting it to zero is that the value is not known by the extractor, so, using the builder, you make a partial instantiation explicit.
To begin with I would like to say sorry for long post, and I really appreciate those who still look into my problem.
I have a controller that should return a json-response with a structure like:
{
result: [
{
key: value,
key: value,
key: value,
key: [
{
key: value,
key: value,
key: value
},...
]
},....
]
}
However I have problems getting the Writes to work as I want.
Note. I will add comments under the line where I have problem.
object APIController extends Controller {
def feed() = Action {
val objects = repo.getObjects().toList
Ok(Json.toJson(Json.obj("result" -> Class_1.apply(objects).result)))
}
first off, if I don't make a Json.obj("result" -> List[objects]) the result key isn't shown in the JSON-result. If I add a Writer for that I get errors saying that the List[objects] must have a Writer. But if I write it like above it doesn't need a Writer for the List[objects]
case class Class_1 (result: Seq[Class_2])
object Class_1 {
def apply(objs: List[Object]) = {
var result:ListBuffer[Class_2] = ListBuffer[Class_2]()
for(obj <- objs) feedResult += Class_2.apply(code)
new Class_1(result.toList)
}
}
*this is where I would put the Writer for Class_1. But if I do this like
implicit val class1Writer = new Writes[Class_1] {
override def writes(o: Class_1): JsValue = Json.obj("result" -> o.result)
} I get the problems I mentioned earlier, that I suddenly need a Writes for a List[objects] of that type*
case class Class_2 (id: Long, id2: Long, listOfStuff: Seq[Class_3])
object Class_2 {
def apply(obj: Object) = {
var items: ListBuffer[Class_3] = ListBuffer[Class_3]()
for(obj1 <- obj.getListOfStuff()) items += Class_3.apply(obj1)
new Class_2(obj.firstID, obj.secID, items.toList)
}
}
implicit val class2Writes = new Writes[Class_2] {
override def writes(o: Class_2): JsValue = {
Json.obj(
"id" -> o.id,
"id2" -> o.id2,
"items" -> o.listOfStuff
)
}
}
*the "items" -> o.listOfStuff says it needs a Writes for a List[types in the list] but I have a Writes for the objects in the list (Class_3) and I don't need a Writes for when serializing a list of objects from Class_2, why is it behaving like this?*
case class Class_3 (id: Long, text: String)
object Class_3 {
def apply(obj: Object) = {
new Class_3(obj.id, obj.funnyText)
}
}
implicit val class3Writer = new Writes[Class_3] {
override def writes(o: Class_3): JsValue = {
Json.obj(
"id" -> o.id,
"text" -> o.text
)
}
}
}
The error I get from this code is:
No Json deserializer found for type Seq[Class_3]. Try to implement an implicit Writes or Format for this type.
[error] "items" -> o.listOfStuff
[error] ^
If I remove this line in the Writes it compiles and works.
And I think that's weird since the first list I serialize doesn't have a Writer, only for the objects in the list.
Does anyone know why it behaves like this?
What should I do to accomplish what I'm after? (I hope you see what I'm trying to do)
Thanks in advance.
Just put the implicit val class3Writer ahead of class2Writes
I am working on implementation of library system using play framework and angularjs.
suppose to search for a book in the library the user enters the keyword value in the input field, this value is received by the controller from the GET request. I need to search the MySQL database for the list of the books, convert them to json request and display them back in the search page which is implemented using angularjs.
I don't understand how to use json and send the result back to the web page.
GET /books/all/search/:by/:value controllers.Books.listBooks(by: String, value: String)
case class Book (
bookId: String,
title: String,
author: String,
category:String,
price: Int,
location: String,
status: String
)
object Book{
val bookParse = {
get[String]("book.bookId") ~
get[String]("book.title") ~
get[String]("book.author") ~
get[String]("book.category") ~
get[Int]("book.price") ~
get[String]("book.location") ~
get[String]("book.status")map {
case bookId~title~author~category~price~location~status => Book(bookId,title, author, category, price, location, status)
}
}
def searchByBookId(bookId: String) : List[Book]= {
DB.withConnection {implicit connection =>
SQL("select * from book where bookId = {bookId}").as(Book.bookParse *)
}
}
object Books extends Controller {
def listBooks(by: String, value:String): List[Book] =
{
if (by == "byBookId" ) Book.searchByBookId(value)
else if(by == "byTitle")Book.searchByTitle(value)
else Book.searchByAuthor(value)
}
}
Now i need to send the List[Book] result to the web page
import play.api.libs.json._
implicit val bookFormat = Json.format[Book]
def listBooks(by: String, value: String) = Action {
val books = if (by == "byBookId" ) Book.searchByBookId(value)
else if(by == "byTitle")Book.searchByTitle(value)
else Book.searchByAuthor(value)
Ok(Json.toJson(books))
}
The implicit val bookFormat needs to be either on the Book companion object, or in scope when Json.toJson is called.
More documentation on JSON:
http://www.playframework.com/documentation/2.2.x/ScalaJson