json4s object extraction with extra data - json

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.

Related

How to Map One-To-Many Relation to JSON Model in Slick

I have 2 classes like this
case class Employee(id: Long,
name: String,
prefix: Option[String],
role: String)
case class Report(id: Long,
name: String,
employee_id: Long)
expecting JSON format
{
"id":1,
"name":"employee",
"prefix":"emp",
"role":"eng",
"reports":[
{
"id":1,
"name":"report_1"
},
{
"id":2,
"name":"report_2"
}
]
}
an employee can have many reports (one-to-many) and i have tables connected with foreign key my questions in which way to retrieve data from database i tried inner join like this
val query: PostgresProfile.api.type#TableQuery[T]
def getAllQuery = {
query.filter(_.isDeleted === false)
}
================================================
def getAllEmployee: Future[Seq[Employee]] = {
val joinQuery = super.getAllQuery.join(reportRepo.getAllQuery).on(_.id === _.employee_id)
val joinRes: Future[Seq[(Employee, Report)]] = db.run(joinQuery.result)
joinRes map { tupleList =>
tupleList.map { tuple =>
Employee(tuple._1.id, tuple._1.name, tuple._1.prefix, tuple._1.role)
}
}
}
i received the data and when i tried to map to JSON it comes with duplicate values too. can anyone suggest way to map or better way to retrieve data from DB with an example.
sorry if i made any mistakes i'm new to playframework and thanks is advance.
The thing which you are looking is groupBy, I guess.
You fetch the data by your join query and groupBy on the employee's ID.
val query = employee.filter(_.yourField === yourCondition).join(report).on(_.id === _.employee_id)
val joinRes = db.run(query.to[List].result)
joinRes.map { list =>
list.groupBy {
case (emp, report) => emp.id
}
}
or
joinRes.map(_.groupBy(_._1.id))
Now
where employee is TableQuery object of employee and report is TableQuery object of report.
Give it a try :)
To get desired json you need to map result into appropriate case class.
case class EmployeeWithReports(id: Long,
name: String,
prefix: Option[String],
role: String,
reports: List[Report])
object EmployeeWithReports {
implicit val jsonFormat = Json.format[EmployeeWithReports]
}
Mapping to this case class can be done in DBIOAction. Firstly group result by employee id and than map all map entries (Int, Seq[Employee, Report]) to EmployeeWithReports
val query = super.getAllQuery
.join(reportRepo.getAllQuery)
.on(_.id === _.employee_id)
val action = query.result.map(rows => {
rows.groupBy(_._1.id).map { mapEntry =>
val (id, name, prefix, role) = mapEntry._2.head._1
EmployeeWithReports(id, name, prefix, role, mapEntry._2.flatMap(_._2).toList)
}.toList
})
Then you can get result calling
val employees: Future[List[EmployeeWithReports]] = db.run(action)

Failing to use transactions in Quill to INSERT one-to-many relational objects

I have a person table and animal table and in the animal table there is FK to personId since there is one-to-many relation between them.
I just want to create a person and create its animals using a transaction cause I want the process to be atomic (there is no use of person in the db if I could not create its animals)
This is the model of how I accept a person creation request:
case class PersonCreateRequest(name: String, age: Int, animals: Seq[AnimalCreateRequest])
This is how the DB knows a Person:
case class Person(personId: Long, name, age: Int)
// this is just a companion object to help me take a PersonCreateRequest and make it Person
object Person {
def apply(person: PersonCreateRequest): Person = {
Person(0L,
person.name,
person.age)
}
}
same thing I have with Animal:
case class AnimalCreateRequest(animalType: String, age: Int)
This is how the db knows a Animal(personId = owner):
case class Animal(animalId: Long, animalType: String, age: Int, personId: Long)
// here I need to get personId as parameter cause I will only have it after a person was created:
object Animal {
def apply(animal: AnimalCreateRequest, personId: Long): Animal = {
Animal(0L,
animal.animalType,
animal.age,
personId)
}
}
So now this is how I tried to do it(and failed):
lazy val ctx = new MysqlAsyncContext(CamelCase, "ctx")
import ctx._
def insertPerson(personToCreate: PersonCreateRequest): Future[Long] = {
// getting the person object that the db knows
val dbPerson = Person.apply(personToCreate)
// INSERT Person Query
val insertPersonQuery = quote {
query[Person].insert(lift(dbPerson)).returning(_.personId)
}
ctx.transaction { implicit ec =>
for {
personId <- ctx.run(insertPersonQuery)
contactIds <- {
Future.sequence(
personToCreate.animals.map(animal => {
val animalToInsert = Animal.apply(animal, personId)
insertAnimal(animalToInsert)
})
)
}
} yield personId
}
}
def insertAnimal(animal: Animal): Future[Long] = {
val q = quote {
query[Animal].insert(lift(animal)).returning(_.animalId)
}
ctx.run(q)
}
What happens is that I just dont get a response...its keep processing without returning anything or throwing an error
Problem was, currently, Quill async does not support concurrent operations inside transactions.
So had to do the animal insertion sequentially:
ctx.transaction { implicit ec =>
for {
personId <- ctx.run(insertPersonQuery)
animals = personCreate.animals.map(Animal.apply(personId, _))
_ <- animals.foldLeft(Future.successful(0l)) {
case (fut, animal) =>
fut.flatMap(_ => insertAnimal(animal))
}
} yield personId
}
also, even better is to use batch insertion :)
Thanks for #fwbrasil and #mentegy for the assistance!
Add implicit ExecutionContext parameter to insertAnimal method:
def insertAnimal(animal: Animal)(implicit ec: ExecutionContext): Future[Long] =
Without it, you're not passing ec from the transaction block and animal insertions will try and use other connections from the pool.
Are you familiar with Scala Futures?
To get a result from a transaction you should add onSuccess handler to a Future returned from the ctx.transaction call:
ctx.transaction { ...
}.onSuccess {
case personId => ...
}

Scala - Can I define a function that receives any function as a parameter?

Is it possible, in Scala, to define a function that would receive any other function as a parameter?
It should be something like the following:
object Module extends SecureModule{
val bc = new MyBC()
def method(parameter: Type) = {
exec(bc.method(parameter))
}
def method2(parameter1: Type1, parameter2: Type2) = {
exec(bc.method2(parameter1,parameter2))
}
}
trait SecureModule {
def exec(f: ANY_PARAMETER => ANY_RESULT) = {
//some extra processing
f
}
}
is it possible? If so, how could I achieve this?
Thank you in advance.
The nice thing about scala is that you can create what seems to be your own syntax.
If what you want to do is wrap an operation so that you can do pre and post processing, as well as control the execution context, then you do this by using call-by-name parameters. For example, if we just wanted to time how long a block of code takes, then we could do something like this:
def timer[T](block: => T): (T,Long) = {
val startDate = new Date()
val result = block
val endDate = new Date()
(result, endDate.getTime()-startDate.getTime())
}
We can use it like this:
val (result,duration) = timer {
1+3
}
Or like this
val (result,duration) = timer {
"hello" + " world!"
}
And the result will have the correct type from the block that you pass in while also giving you the duration that you expect.
I am under the impression that your description is somewhat misleading.
The way I understand it, what you (might) want to do is delaying the execution of the bc.method calls until some other code has been performed.
If so, try this:
object Module extends SecureModule{
val bc = new MyBC()
def method(parameter: Type) = {
exec(() => bc.method(parameter))
}
def method2(parameter1: Type1, parameter2: Type2) = {
exec(() => bc.method2(parameter1,parameter2))
}
}
trait SecureModule {
def exec[Result](f: () => Result): Result = {
//some extra processing
f()
}
}
You can't take any function as a parameter. What would you even do it?
At best, you can take any function that has a specific number of parameters.
For example, here, f takes one argument and returns a value.
def exec[A,B](f: A => B)
And here, f takes two arguments:
def exec[A,B,C](f: (A, B) => C)
If you don't care about the return type of the function, you could always use Any instead of a type parameter, since functions are covariant in their return type:
def exec[A](f: A => Any)

Render json data to the view with play! scala 2.2.3

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

Having trouble getting Writes to work with Scala Play

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