I'm trying to use Spray JSON to marshall the 'Seq' collection below into a 'BidRequest' entity with the parameters as defined.
The Seq collection is mostly nested, therefore some 'Seq' parameter fields also have variable collection types that need to be marshalled.
Then after a computation, the aim is to unmarshall the results as an entity of 'BidResponse'.
What's the best approach to do this?
I'm using Akka-HTTP, Akka-Streams, Akka-Actor.
Seq collection:
val activeUsers = Seq(
Campaign(
id = 1,
country = "UK",
targeting = Targeting(
targetedSiteIds = Seq("0006a522ce0f4bbbbaa6b3c38cafaa0f")
),
banners = List(
Banner(
id = 1,
src ="https://business.URLTV.com/wp-content/uploads/2020/06/openGraph.jpeg",
width = 300,
height = 250
)
),
bid = 5d
)
)
BidRequest case class:
case class BidRequest(id: String, imp: Option[List[Impression]], site:Site, user: Option[User], device: Option[Device])
BidResponse case class:
case class BidResponse(id: String, bidRequestId: String, price: Double, adid:Option[String], banner: Option[Banner])
The other case classes:
case class Campaign(id: Int, country: String, targeting: Targeting, banners: List[Banner], bid: Double)
case class Targeting(targetedSiteIds: Seq[String])
case class Banner(id: Int, src: String, width: Int, height: Int)
case class Impression(id: String, wmin: Option[Int], wmax: Option[Int], w: Option[Int], hmin: Option[Int], hmax: Option[Int], h: Option[Int], bidFloor: Option[Double])
case class Site(id: Int, domain: String)
case class User(id: String, geo: Option[Geo])
case class Device(id: String, geo: Option[Geo])
case class Geo(country: Option[String])
I've so far tried using the code below but keep getting type mismatch errors:
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
implicit val resFormat = jsonFormat2(BidResponse)
implicit val bidFormat = jsonFormat1(BidRequest)
implicit val cFormat = jsonFormat1(Campaign)
implicit val tFormat = jsonFormat1(Targeting)
implicit val bFormat = jsonFormat1(Banner)
implicit val iFormat = jsonFormat1(Impression)
implicit val sFormat = jsonFormat1(Site)
implicit val uFormat = jsonFormat1(User)
implicit val dFormat = jsonFormat1(Device)
implicit val gFormat = jsonFormat1(Geo)
The reason why you are getting Type errors with Spray JSON is because you need to use the corresponding jsonFormatN method, depending on the number of parameters in the case class.
In your case:
implicit val resFormat = jsonFormat5(BidResponse)
implicit val bidFormat = jsonFormat5(BidRequest)
implicit val cFormat = jsonFormat1(Campaign)
implicit val tFormat = jsonFormat1(Targeting)
implicit val bFormat = jsonFormat4(Banner)
...
Related
I have the following 3 case classes:
case class Profile(name: String,
age: Int,
bankInfoData: BankInfoData,
userUpdatedFields: Option[UserUpdatedFields])
case class BankInfoData(accountNumber: Int,
bankAddress: String,
bankNumber: Int,
contactPerson: String,
phoneNumber: Int,
accountType: AccountType)
case class UserUpdatedFields(contactPerson: String,
phoneNumber: Int,
accountType: AccountType)
this is just enums, but i added anyway:
sealed trait AccountType extends EnumEntry
object AccountType extends Enum[AccountType] {
val values: IndexedSeq[AccountType] = findValues
case object Personal extends AccountType
case object Business extends AccountType
}
my task is - i need to write a funcc Profile and compare UserUpdatedFields(all of the fields) with SOME of the fields in BankInfoData...this func is to find which fields where updated.
so I wrote this func:
def findDiff(profile: Profile): Seq[String] = {
var listOfFieldsThatChanged: List[String] = List.empty
if (profile.bankInfoData.contactPerson != profile.userUpdatedFields.get.contactPerson){
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "contactPerson"
}
if (profile.bankInfoData.phoneNumber != profile.userUpdatedFields.get.phoneNumber) {
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "phoneNumber"
}
if (profile.bankInfoData.accountType != profile.userUpdatedFields.get.accountType) {
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "accountType"
}
listOfFieldsThatChanged
}
val profile =
Profile(
"nir",
34,
BankInfoData(1, "somewhere", 2, "john", 123, AccountType.Personal),
Some(UserUpdatedFields("lee", 321, AccountType.Personal))
)
findDiff(profile)
it works, but wanted something cleaner..any suggestions?
Each case class extends Product interface so we could use it to convert case classes into sets of (field, value) elements. Then we can use set operations to find the difference. For example,
def findDiff(profile: Profile): Seq[String] = {
val userUpdatedFields = profile.userUpdatedFields.get
val bankInfoData = profile.bankInfoData
val updatedFieldsMap = userUpdatedFields.productElementNames.zip(userUpdatedFields.productIterator).toMap
val bankInfoDataMap = bankInfoData.productElementNames.zip(bankInfoData.productIterator).toMap
val bankInfoDataSubsetMap = bankInfoDataMap.view.filterKeys(userUpdatedFieldsMap.keys.toList.contains)
(bankInfoDataSubsetMap.toSet diff updatedFieldsMap.toSet).toList.map { case (field, value) => field }
}
Now findDiff(profile) should output List(phoneNumber, contactPerson). Note we are using productElementNames from Scala 2.13 to get the filed names which we then zip with corresponding values
userUpdatedFields.productElementNames.zip(userUpdatedFields.productIterator)
Also we rely on filterKeys and diff.
A simple improvement would be to introduce a trait
trait Fields {
val contactPerson: String
val phoneNumber: Int
val accountType: AccountType
def findDiff(that: Fields): Seq[String] = Seq(
Some(contactPerson).filter(_ != that.contactPerson).map(_ => "contactPerson"),
Some(phoneNumber).filter(_ != that.phoneNumber).map(_ => "phoneNumber"),
Some(accountType).filter(_ != that.accountType).map(_ => "accountType")
).flatten
}
case class BankInfoData(accountNumber: Int,
bankAddress: String,
bankNumber: Int,
contactPerson: String,
phoneNumber: Int,
accountType: String) extends Fields
case class UserUpdatedFields(contactPerson: String,
phoneNumber: Int,
accountType: AccountType) extends Fields
so it was possible to call
BankInfoData(...). findDiff(UserUpdatedFields(...))
If you want to further-improve and avoid naming all the fields multiple times, for example shapeless could be used to do it compile time. Not exactly the same but something like this to get started. Or use reflection to do it runtime like this answer.
That would be a very easy task to achieve if it would be an easy way to convert case class to map. Unfortunately, case classes don't offer that functionality out-of-box yet in Scala 2.12 (as Mario have mentioned it will be easy to achieve in Scala 2.13).
There's a library called shapeless, that offers some generic programming utilities. For example, we could write an extension function toMap using Record and ToMap from shapeless:
object Mappable {
implicit class RichCaseClass[X](val x: X) extends AnyVal {
import shapeless._
import ops.record._
def toMap[L <: HList](
implicit gen: LabelledGeneric.Aux[X, L],
toMap: ToMap[L]
): Map[String, Any] =
toMap(gen.to(x)).map{
case (k: Symbol, v) => k.name -> v
}
}
}
Then we could use it for findDiff:
def findDiff(profile: Profile): Seq[String] = {
import Mappable._
profile match {
case Profile(_, _, bankInfo, Some(userUpdatedFields)) =>
val bankInfoMap = bankInfo.toMap
userUpdatedFields.toMap.toList.flatMap{
case (k, v) if bankInfoMap.get(k).exists(_ != v) => Some(k)
case _ => None
}
case _ => Seq()
}
}
With reference to the link below
Scala: curried constructors
class Person(var name : String, var age : Int, var email : String)
def mkPerson = (n : String) => (a : Int) => (e : String) => new Person(n,a,e)
def makeName(s:String):String="Hello"+s
def makeAge(age:Int):Int=age
def makeEmail(email:String):String=email
val s=mkPerson(makeName("abcd"))
val t1=s(makeAge(2))
val t2=t1(makeEmail("abc#gmail.com"))
I have created methods makeName,makeAge,makeEmail to enrich the values. The actual use case is different
Question
Is there any possible ways to achieve the above through case classes
I don't want to use variables s,t1,t2
Can we achieve the above by Partially applied functions
Not sure curried functions are necessary / helpful in this case, if you're OK with a mkPerson that takes 3 arguments:
case class Person private(name: String, age: Int, email: String)
def makeName(s: String): String = "Hello" + s
def makeAge(age: Int): Int = age
def makeEmail(email: String): String = email
def mkPerson(name: String, age: Int, email: String): Person =
Person(makeName(name), makeAge(age), makeEmail(email))
println(Person("Jane", 29, "jane#mail.com")) // no enrichment: Person(Jane,29,jane#mail.com)
println(mkPerson("Jane", 29, "jane#mail.com")) // enriched: Person(HelloJane,29,jane#mail.com)
Then, if you really need it, you can curry mkPerson:
val curried = (mkPerson _).curried
curried("Jane")(29)("jane#mail.com") // same enriched result
I have about 24 Case classes that I need to programatically enhance by changing several common elements prior to serialization in a datastore that doesn't support joins. Since case classes don't have a trait defined for the copy(...) constructor, I have been attempting to use Macros - As a base I've looked at
this post documenting a macro and come up with this macro:
When I try to compile, I get the following:
import java.util.UUID
import org.joda.time.DateTime
import scala.language.experimental.macros
trait RecordIdentification {
val receiverId: Option[String]
val transmitterId: Option[String]
val patientId: Option[UUID]
val streamType: Option[String]
val sequenceNumber: Option[Long]
val postId: Option[UUID]
val postedDateTime: Option[DateTime]
}
object WithRecordIdentification {
import scala.reflect.macros.Context
def withId[T, I](entity: T, id: I): T = macro withIdImpl[T, I]
def withIdImpl[T: c.WeakTypeTag, I: c.WeakTypeTag](c: Context)(
entity: c.Expr[T], id: c.Expr[I]
): c.Expr[T] = {
import c.universe._
val tree = reify(entity.splice).tree
val copy = entity.actualType.member(newTermName("copy"))
val params = copy match {
case s: MethodSymbol if (s.paramss.nonEmpty) => s.paramss.head
case _ => c.abort(c.enclosingPosition, "No eligible copy method!")
}
c.Expr[T](Apply(
Select(tree, copy),
AssignOrNamedArg(Ident("postId"), reify(id.splice).tree) ::
AssignOrNamedArg(Ident("patientId"), reify(id.splice).tree) ::
AssignOrNamedArg(Ident("receiverId"), reify(id.splice).tree) ::
AssignOrNamedArg(Ident("transmitterId"), reify(id.splice).tree) ::
AssignOrNamedArg(Ident("sequenceNumber"), reify(id.splice).tree) :: Nil
))
}
}
And I invoke it with something like:
class GenericAnonymizer[A <: RecordIdentification]() extends Schema {
def anonymize(dataPost: A, header: DaoDataPostHeader): A = WithRecordIdentification.withId(dataPost, header)
}
But I get a compile error:
Error:(44, 71) type mismatch;
found : com.dexcom.rt.model.DaoDataPostHeader
required: Option[String]
val copied = WithRecordIdentification.withId(sampleGlucoseRecord, header)
Error:(44, 71) type mismatch;
found : com.dexcom.rt.model.DaoDataPostHeader
required: Option[java.util.UUID]
val copied = WithRecordIdentification.withId(sampleGlucoseRecord, header)
Error:(44, 71) type mismatch;
found : com.dexcom.rt.model.DaoDataPostHeader
required: Option[Long]
val copied = WithRecordIdentification.withId(sampleGlucoseRecord, header)
I'm not quite sure how to change the macro to support multiple parameters... any sage advice?
Assuming you have a set of following case classes, which you wish to anonymize on certain attributes prior to serialization.
case class MyRecordA(var receiverId: String, var y: Int)
case class MyRecordB(var transmitterId: Int, var y: Int)
case class MyRecordC(var patientId: UUID, var y: Int)
case class MyRecordD(var streamType: String, var y: Int)
case class MyRecordE(var sequenceNumber: String, var streamType: String, var y: Int)
You can use scala reflection library to mutate an instance's attributes in runtime. You can implement your custom anonymize/enhancing logic in implicit anonymize method that the Mutator can use to alter a given instance's field selectively if required as per your implementation.
import java.util.UUID
import scala.reflect.runtime.{universe => ru}
implicit def anonymize(field: String /* field name */, value: Any /* use current field value if reqd */): Option[Any] = field match {
case "receiverId" => Option(value.toString.hashCode)
case "transmitterId" => Option(22)
case "patientId" => Option(UUID.randomUUID())
case _ => None
}
implicit class Mutator[T: ru.TypeTag](i: T)(implicit c: scala.reflect.ClassTag[T], anonymize: (String, Any) => Option[Any]) {
def mask = {
val m = ru.runtimeMirror(i.getClass.getClassLoader)
ru.typeOf[T].members.filter(!_.isMethod).foreach(s => {
val fVal = m.reflect(i).reflectField(s.asTerm)
anonymize(s.name.decoded.trim, fVal.get).foreach(fVal.set)
})
i
}
}
Now you can invoke masking on any instance as:
val maskedRecord = MyRecordC(UUID.randomUUID(), 2).mask
I'm trying to map an ADT (case classes inheriting from a sealed trait) as multiple columns of the same table, using Slick, something like:
sealed trait OrValue
case class IntValue(value: Int)
case class StringValue(value: String)
case class Thing(id: Int, value: OrValue)
class Things(tag: Tag) extends Table[Thing](tag, "things") {
def id = column[Int]("id", O.PrimaryKey)
def intValue = column[Option[Int]]("intValue")
def stringValue = column[Option[String]]("stringValue")
def toThing(_id: String, _intValue: Option[Int], _stringValue: Option[String]): Thing = Thing(id, ((_intValue, _stringValue) match {
case (Some(a), None) => IntValue(a)
case (None, Some(a)) => StringValue(a)
}
def fromThing(t: Thing): (String, Option[Int], Option[String]) = ??? // elided
def * = (id, intValue, stringValue) <> ((toThing _).tupled, fromThing)
}
This is not compling:
[error] found : [U]slick.lifted.MappedProjection[Thing,U]
[error] required: slick.lifted.ProvenShape[Thing]
[error] ) <> ((toThing _).tupled, fromThing _)
Am I approaching this the wrong way?
What's the idiomatic way of representing an ADT?
The problem is in signature of methods toThing and fromThing.
It should look so:
sealed trait OrValue
case class IntValue(value: Int)
case class StringValue(value: String)
case class Thing(id: Int, value: OrValue)
class Things(tag: Tag) extends Table[Thing](tag, "things") {
def id = column[Int]("id", O.PrimaryKey)
def intValue = column[Option[Int]]("intValue")
def stringValue = column[Option[String]]("stringValue")
def toThing(source: (Int, Option[Int], Option[String])): Thing = source match {
case (id, intValue, stringValue) => ??? //TODO implement
}
def fromThing(t: Thing): Option[(Int, Option[Int], Option[String])] = ??? //TODO implement
def * = (id, intValue, stringValue) <> (toThing _, fromThing _)
}
You shouldn't name mapping and case classes with the same name. Change map class to Things, for example.
How can I look up the value of an object's property dynamically by name in Scala 2.10.x?
E.g. Given the class (it can't be a case class):
class Row(val click: Boolean,
val date: String,
val time: String)
I want to do something like:
val fields = List("click", "date", "time")
val row = new Row(click=true, date="2015-01-01", time="12:00:00")
fields.foreach(f => println(row.getProperty(f))) // how to do this?
class Row(val click: Boolean,
val date: String,
val time: String)
val row = new Row(click=true, date="2015-01-01", time="12:00:00")
row.getClass.getDeclaredFields foreach { f =>
f.setAccessible(true)
println(f.getName)
println(f.get(row))
}
You could also use the bean functionality from java/scala:
import scala.beans.BeanProperty
import java.beans.Introspector
object BeanEx extends App {
case class Stuff(#BeanProperty val i: Int, #BeanProperty val j: String)
val info = Introspector.getBeanInfo(classOf[Stuff])
val instance = Stuff(10, "Hello")
info.getPropertyDescriptors.map { p =>
println(p.getReadMethod.invoke(instance))
}
}