create custom JsObject out of case class - scala

im getting in my api some case class that looks like this:
case class UserUpdate(
name: Option[String],
age: Option[String],
city: Option[String])
from this case class I need to build update json for mongo that looks like:
{"basicInfo.name": "new nameeee","age.vatId": "23"}
since all the fields are optional, I need to go over the fields and build it by the ones that are defined.
so I did something like this:
val updateAsMap = Json.toJson(userUpdateObj).as[JsObject]
.fields.map(fieldTpl => fieldTpl.copy(_1 = s"basicInfo.${fieldTpl._1}")).toMap
val userUpdateJson = Json.toJson(updateAsMap)
val query = json("userId" -> userId)
val update = json("$set" -> userUpdateJson)
is there a better suggestion, something that will look more elegant?

One option could be to convert UserUpdate to an intermediary case class, say MongoModel, and then convert MongoModel to JSON. For example,
import play.api.libs.json._
case class MongoModel(
`basicInfo.name`: Option[String] = None,
`age.vatId`: Option[String] = None
)
object MongoModel {
implicit val codec = Json.format[MongoModel]
}
case class UserUpdate(
name: Option[String] = None,
age: Option[String] = None,
city: Option[String] = None
) {
def toMongoModel =
MongoModel(
`basicInfo.name` = name,
`age.vatId` = age
)
}
val userUpdate = UserUpdate(name = Some("new nameeee"), age = Some("23"))
Json.toJson(userUpdate.toMongoModel) // {"basicInfo.name": "new nameeee", "age.vatId": "23"}: JsValue
Note the usage of backticks in MongoModel.

Related

Marshalling nested custom Objects in Sangria

I have the following Input Objects:
val BusinessInputType = InputObjectType[BusinessInput]("BusinessInput", List(
InputField("userId", StringType),
InputField("name", StringType),
InputField("address", OptionInputType(StringType)),
InputField("phonenumber", OptionInputType(StringType)),
InputField("email", OptionInputType(StringType)),
InputField("hours", ListInputType(BusinessHoursInputType))
))
val BusinessHoursInputType = InputObjectType[BusinessHoursInput]("hours", List(
InputField("weekDay", IntType),
InputField("startTime", StringType),
InputField("endTime", StringType)
))
And here are my models with custom Marshalling defined:
case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])
object BusinessInput {
implicit val manual = new FromInput[BusinessInput] {
val marshaller = CoercedScalaResultMarshaller.default
def fromResult(node: marshaller.Node) = {
val ad = node.asInstanceOf[Map[String, Any]]
System.out.println(ad)
BusinessInput(
userId = ad("userId").asInstanceOf[String],
name = ad("name").asInstanceOf[String],
address = ad.get("address").flatMap(_.asInstanceOf[Option[String]]),
phonenumber = ad.get("phonenumber").flatMap(_.asInstanceOf[Option[String]]),
email = ad.get("email").flatMap(_.asInstanceOf[Option[String]]),
hours = ad("hours").asInstanceOf[Seq[BusinessHoursInput]]
)
}
}
}
case class BusinessHoursInput(weekDay: Int, startTime: Time, endTime: Time)
object BusinessHoursInput {
implicit val manual = new FromInput[BusinessHoursInput] {
val marshaller = CoercedScalaResultMarshaller.default
def fromResult(node: marshaller.Node) = {
val ad = node.asInstanceOf[Map[String, Any]]
System.out.println("HEY")
BusinessHoursInput(
weekDay = ad("weekDay").asInstanceOf[Int],
startTime = Time.valueOf(ad("startTime").asInstanceOf[String]),
endTime = Time.valueOf(ad("endTime").asInstanceOf[String])
)
}
}
}
My question is, When I have a nested InputObject that has custom Marshalling, I dont see the marshalling of BusinessHoursInput getting invoked before the BusinessInput is marshalled. I noticed this because the print statement of "Hey" is never executed before the print statement of "ad" in BusinessInput. This causes problems later down the road for me when I try to insert the hours field of BusinessInput in the DB because it cannot cast it to BusinessHoursInput object. In Sangria, is it not possible to custom Marshal nested Objects before the parent Object is marshalled?
You are probably are using BusinessInput as an argument type. The actual implicit lookup takes place at the Argument definition time and only for BusinessInput type.
Since FromInput is a type-class based deserialization, you need to explicitly define the dependency between deserializers of nested object. For example, you can rewrite the deserializer like this:
case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])
object BusinessInput {
implicit def manual(implicit hoursFromInput: FromInput[BusinessHoursInput]) = new FromInput[BusinessInput] {
val marshaller = CoercedScalaResultMarshaller.default
def fromResult(node: marshaller.Node) = {
val ad = node.asInstanceOf[Map[String, Any]]
BusinessInput(
userId = ad("userId").asInstanceOf[String],
name = ad("name").asInstanceOf[String],
address = ad.get("address").flatMap(_.asInstanceOf[Option[String]]),
phonenumber = ad.get("phonenumber").flatMap(_.asInstanceOf[Option[String]]),
email = ad.get("email").flatMap(_.asInstanceOf[Option[String]]),
hours = hoursFromInput.fromResult(ad("hours").asInstanceOf[Seq[hoursFromInput.marshaller.Node]])
)
}
}
}
In this version, I'm taking advantage of existing FromInput[BusinessHoursInput] to deserialize BusinessHoursInput from the raw input.
Also as an alternative, you can avoid defining manual FromInput deserializers altogether by taking advantage of existing JSON-based deserializers. For example, in most cases, circe's automatic derivation works just fine. You just need these 2 imports (in the file where you are defining the arguments):
import sangria.marshalling.circe._
import io.circe.generic.auto._
Those import put appropriate FromInput instances into the scope. These instances take advantage of circe's own deserialization mechanism.
import io.circe.Decoder
import io.circe.generic.semiauto.deriveDecoder
import sangria.macros.derive.deriveInputObjectType
import sangria.marshalling.circe._
import sangria.schema.{Argument, InputObjectType}
object XXX {
// when you have FromInput for all types in case class (Int, String) you can derive it
case class BusinessHoursInput(weekDay: Int, startTime: String, endTime: String)
object BusinessHoursInput {
implicit val decoder: Decoder[BusinessHoursInput] = deriveDecoder
implicit val inputType: InputObjectType[BusinessHoursInput] = deriveInputObjectType[BusinessHoursInput]()
}
// the same here, you need InputObjectType also for BusinessHoursInput
case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])
object BusinessInput {
implicit val decoder: Decoder[BusinessInput] = deriveDecoder
implicit val inputType: InputObjectType[BusinessInput] = deriveInputObjectType[BusinessInput]()
}
// for this to work you need to have in scope InputType BusinessInput and FromInput for BusinessInput
// FromInput you can get by having Decoder in scope and import sangria.marshalling.circe._
private val businessInputArg = Argument("businessInput", BusinessInput.inputType)
}
id you do not use circe but different json library you should have of course different typeclasses and proper import in scope

In Slick, how to filter based on a custom column properties?

I have a custom column in my Slick as follow:
class PgACL(tag: Tag) extends Table[ACL](tag, Some(schemaName), "ACL") {
def id = column[UUID]("ID", O.PrimaryKey)
def resourceSpec = column[ResourceSpec]("RESOURCE_SPEC")
def * = (id, resourceSpec) <>(ACL.tupled, ACL.unapply)
}
and the custom class:
case class ResourceSpec (val resourceType: String, val resourceId: String)
And I map them like this:
implicit val ResourceSpecMapper = MappedColumnType.base[ResourceSpec, String](
resourceSpec => resourceSpec.resourceSpecStr,
str => ResourceSpec.fromString(str)
)
I am trying to have a query which filter based on a property of the custom column, but I don't know how I can access to it. For example I want to have:
TableQuery[PgACL].filter(x => x.resourceSpec.resourceType === "XYZ")
But x.resourceSpec returns Rep[ResourceSpec] and I don't know how to get its "resourceType" property.
Any help?

Scala reduce on Case class with keys

Below is the scenario of 2 case classes that I have -
case class Pack(name: String, age: Int, dob: String)
object Pack{
def getPack(name:String, age: Int, dob:String): Pack = {
Pack(name,age,dob)
}
}
case class NewPack(name: String, pack: (List[Pack]))
object NewPack{
def getNewPackList(data: List[Pack]): List[NewPack] = {
val newData = for(x <- data )yield(x.name,List(x))
val newPackData = for(x <- newData)yield(NewPack(x._1,x._2))
newPackData
}
}
val someData = List(Pack.getPack("x",12,"day1"), Pack.getPack("y",23,"day2"),Pack.getPack("x",34,"day3") )
val somePackData = NewPack.getNewPackList(someData)
the values of someData and somePackData is like this -
someData: List[Pack] = List(Pack(x,12,day1), Pack(y,23,day2), Pack(x,34,day3))
somePackData: List[NewPack] = List(NewPack(x,List(Pack(x,12,day1))), NewPack(y,List(Pack(y,23,day2))), NewPack(x,List(Pack(x,34,day3))))
Now I want to get the final data in the below mentioned format. Could anyone suggest me the better way of doing that.
finalData: List[NewPack] = List(NewPack(x,List(Pack(x,12,day1),Pack(x,34,day3)),NewPack(y,List(Pack(y,23,day2))))
Here is small simplification.
case class Pack(name: String, age: Int, dob: String)
case class NewPack(name: String, pack: List[Pack])
object NewPack{
def getNewPackList(data: List[Pack]): List[NewPack] =
data.map{case pack#Pack(name, _, _) => NewPack(name, List(pack))}
}
Note that in general you don't need additional factory method for case class. If you would like to present something tricky later, you can always define custom apply in your companion object.
Additionally if you were intending to group packs by name you can easily define something like
def newPacksGrouped(data: List[Pack]): List[NewPack] =
data.groupBy(_.name).map{case (name, packs) => NewPack(name, packs)}.toList

Adding functionality before calling constructor in extra constructor

Is it possible to add functionality before calling constructor in extra constructor in scala ?
Lets say, I have class User, and want to get one string - and to split it into attributes - to send them to the constructor:
class User(val name: String, val age: Int){
def this(line: String) = {
val attrs = line.split(",") //This line is leading an error - what can I do instead
this(attrs(0), attrs(1).toInt)
}
}
So I know I'm not able to add a line before sending to this, because all constructors need to call another constructor as the first statement of the constructor.
Then what can I do instead?
Edit:
I have a long list of attributes, so I don't want to repeat line.split(",")
I think this is a place where companion object and apply() method come nicely into play:
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
class User(val name: String, val age: Int)
Then you just create your object the following way:
val u1 = User("Zorro,33")
Also since you're exposing name and age anyway, you might consider using case class instead of standard class and have consistent way of constructing User objects (without new keyword):
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
case class User(name: String, age: Int)
val u1 = User("Zorro,33")
val u2 = User("Zorro", "33")
Ugly, but working solution#1:
class User(val name: String, val age: Int){
def this(line: String) = {
this(line.split(",")(0), line.split(",")(1).toInt)
}
}
Ugly, but working solution#2:
class User(val name: String, val age: Int)
object User {
def fromString(line: String) = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
Which can be used as:
val johny = User.fromString("johny,35")
You could use apply in place of fromString, but this will lead to a confusion (in one case you have to use new, in the other you have to drop it) so I prefer to use different name
Another ugly solution:
class User(line: String) {
def this(name: String, age: Int) = this(s"$name,$age")
val (name, age) = {
val Array(nameStr,ageStr) = line.split(",")
(nameStr,ageStr.toInt)
}
}
But using a method of the companion object is probably better.

Slick 2.0: How to convert lifted query results to a case class?

in order to implement a ReSTfull APIs stack, I need to convert data extracted from a DB to JSON format. I think that the best way is to extract data from the DB and then convert the row set to JSON using Json.toJson() passing as argument a case class after having defined a implicit serializer (writes).
Here's my case class and companion object:
package deals.db.interf.slick2
import scala.slick.driver.MySQLDriver.simple._
import play.api.libs.json.Json
case class PartnerInfo(
id: Int,
name: String,
site: String,
largeLogo: String,
smallLogo: String,
publicationSite: String
)
object PartnerInfo {
def toCaseClass( ?? ) = { // what type are the arguments to be passed?
PartnerInfo( fx(??) ) // how to transform the input types (slick) to Scala types?
}
// Notice I'm using slick 2.0.0 RC1
class PartnerInfoTable(tag: Tag) extends Table[(Int, String, String, String, String, String)](tag, "PARTNER"){
def id = column[Int]("id")
def name = column[String]("name")
def site = column[String]("site")
def largeLogo = column[String]("large_logo")
def smallLogo = column[String]("small_logo")
def publicationSite = column[String]("publication_site")
def * = (id, name, site, largeLogo, smallLogo, publicationSite)
}
val partnerInfos = TableQuery[PartnerInfoTable]
def qPartnerInfosForPuglisher(publicationSite: String) = {
for (
pi <- partnerInfos if ( pi.publicationSite == publicationSite )
) yield toCaseClass( _ ) // Pass all the table columns to toCaseClass()
}
implicit val partnerInfoWrites = Json.writes[PartnerInfo]
}
What I cannot get is how to implement the toCaseClass() method in order to transform the column types from Slick 2 to Scala types - notice the function fx() in the body of toCaseClass() is only meant to give emphasis to that.
I'm wondering if is it possible to get the Scala type from Slick column type because it is clearly passed in the table definition, but I cannot find how to get it.
Any idea?
I believe the simplest method here would be to map PartnerInfo in the table schema:
class PartnerInfoTable(tag: Tag) extends Table[PartnerInfo](tag, "PARTNER"){
def id = column[Int]("id")
def name = column[String]("name")
def site = column[String]("site")
def largeLogo = column[String]("large_logo")
def smallLogo = column[String]("small_logo")
def publicationSite = column[String]("publication_site")
def * = (id, name, site, largeLogo, smallLogo, publicationSite) <> (PartnerInfo.tupled, PartnerInfo.unapply)
}
val partnerInfos = TableQuery[PartnerInfoTable]
def qPartnerInfosForPuglisher(publicationSite: String) = {
for (
pi <- partnerInfos if ( pi.publicationSite == publicationSite )
) yield pi
}
Otherwise PartnerInfo.tupled should do the trick:
def toCaseClass(pi:(Int, String, String, String, String, String)) = PartnerInfo.tupled(pi)