I have a case class:
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
value: Option[String])
This was working fine until I have a new use case where "value" parameter can be a class Object instead of String.
My initial implementation to handle this use case:
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
addressId: Option[String],
addressValue: Option[MailingAddress]) {
def this(addressFormat: String, screeningAddressType: String, addressId: String) = {
this(addressFormat, screeningAddressType, Option(addressId), None)
}
def this(addressFormat: String, screeningAddressType: String, address: MailingAddress) = {
this(addressFormat, screeningAddressType, None, Option(address))
}
}
But because of some problem, I can not have four parameters in any constructor.
Is there a way I can create a class containing three parameters: ** addressFormat, screeningAddressType, value** and handle both the use cases?
Your code works fine, to use the other constructor's you just need to use the new keyword:
case class MailingAddress(i: Int)
case class EvaluateAddress(addressFormat: String, screeningAddressType: String, addressId: Option[String], addressValue: Option[MailingAddress]) {
def this(addressFormat: String, screeningAddressType: String, addressId: String) = {
this(addressFormat, screeningAddressType, Option(addressId), None)
}
def this(addressFormat: String, screeningAddressType: String, address: MailingAddress) = {
this(addressFormat, screeningAddressType, None, Option(address))
}
}
val e1 = EvaluateAddress("a", "b", None, None)
val e2 = new EvaluateAddress("a", "b", "c")
val e3 = new EvaluateAddress("a", "b", MailingAddress(0))
You can create an auxilliary ADT to wrap different types of values. Inside EvaluateAddress you can check the alternative that was provided with a match:
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
value: Option[EvaluateAddress.Value]
) {
import EvaluateAddress._
def doEvaluation() = value match {
case Some(Value.AsId(id)) =>
case Some(Value.AsAddress(mailingAddress)) =>
case None =>
}
}
object EvaluateAddress {
sealed trait Value
object Value {
case class AsId(id: String) extends Value
case class AsAddress(address: MailingAddress) extends Value
}
}
It's then possible to also define some implicit conversions to automatically convert Strings and MailingAddresses into Values:
object EvaluateAddress {
sealed trait Value
object Value {
case class AsId(id: String) extends Value
case class AsAddress(address: MailingAddress) extends Value
implicit def idAsValue(id: String): Value = AsId(id)
implicit def addressAsValue(address: MailingAddress): Value = AsAddress(address)
}
def withRawValue[T](addressFormat: String,
screeningAddressType: String,
rawValue: Option[T])(implicit asValue: T => Value): EvaluateAddress =
{
EvaluateAddress(addressFormat, screeningAddressType, rawValue.map(asValue))
}
}
Some examples of using those implicit conversions:
scala> EvaluateAddress("a", "b", Some("c"))
res1: EvaluateAddress = EvaluateAddress(a,b,Some(AsId(c)))
scala> EvaluateAddress("a", "b", Some(MailingAddress("d")))
res2: EvaluateAddress = EvaluateAddress(a,b,Some(AsAddress(MailingAddress(d))))
scala> val id: Option[String] = Some("id")
id: Option[String] = Some(id)
scala> EvaluateAddress.withRawValue("a", "b", id)
res3: EvaluateAddress = EvaluateAddress(a,b,Some(AsId(id)))
Related
I am trying to parse a complex json which has different schema for the same field in a same message. So I am trying to parse it via Either, but play json doesn't supports Either natively. So I tried to write custom reads and write function which has some syntax issue and quite tedious to figure out. Can some one help me with this?
Input json
{
"settings":[
{
"active":false,
"parameter":{
"x1":0,
"x2":"Simple"
},
"type":"sometype"
},
{
"active":true,
"parameter":[
{
"y1":100,
"y2":"east",
"y3":{
"blue":true,
"green":false
}
}
],
"type":"someDifferentType"
}
]
}
I have tried below code which has syntax problem
case class Xparameter(x1: Int, x2: String)
object Xparameter {
implicit val format: Format[Xparameter] = Json.format[Xparameter]
}
case class Yparameter(y1: Int, y2: String, y3: Color)
object Yparameter {
implicit val format: Format[Yparameter] = Json.format[Yparameter]
}
case class Color(blue: Boolean, green: Boolean)
object Color {
implicit val format: Format[Color] = Json.format[Color]
}
case class SettingLeft(
active: Boolean,
`type`: String,
parameter: Xparameter
)
object SettingLeft {
implicit val format: Format[SettingLeft] = Json.format[SettingLeft]
}
case class SettingRight(
active: Boolean,
`type`: String,
parameter: List[Yparameter]
)
object SettingRight {
implicit val format: Format[SettingRight] = Json.format[SettingRight]
}
case class Setting(
active: Boolean,
`type`: String,
parameter: Either[Xparameter, List[Yparameter]]
)
object Setting {
implicit def apply(active: Boolean,
`type`: String,
parameter: Xparameter
): Setting = Setting(active,`type`, Left(parameter))
implicit def apply(active: Boolean,
`type`: String,
parameter: List[Yparameter]
): Setting = Setting(active,`type`, Right(parameter))
implicit val format: Format[Setting] = new Format[Setting] {
override def reads(json: JsValue): JsResult[Setting] = json
.validate[SettingLeft]
.map(SettingLeft.apply) // <-- not sure if its possible to do
.orElse(
json
.validate[SettingRight]
.map(SettingRight.apply)
) // <--- syntax error
override def writes(json: Setting): JsValue = json.either match {
case Left(value) => Json.toJson(value)
case Right(value) => Json.toJson(value) // <--- syntax error
}
}
}
I have 2 case classes:
case class Person(fname: String, lname: String, age: Int, address: String)
case class PersonUpdate(fname: String, lname: String, age: Int)
so PersonUpdate dosent have all the fields Person have, and I want to write effective that get Person and PersonUpdate and find the fields that have different values:
for example:
def findChangedFields(person: Person, personUpdate: PersonUpdate): Seq[String] = {
var listOfChangedFields: List[String] = List.empty
if (person.fname == personUpdate.fname)
listOfChangedFields = listOfChangedFields :+ "fname"
if (person.lname == personUpdate.lname)
listOfChangedFields = listOfChangedFields :+ "lname"
if (person.age == personUpdate.age)
listOfChangedFields = listOfChangedFields :+ "age"
listOfChangedFields
}
findChangedFields(per, perUpdate)
but this is very ugly, how can I write this nicely with the magic of scala?
Something like this, maybe?
val fields = Seq("fname", "lname", "age")
val changedFields = person.productIterator
.zip(personUpdate.productIterator)
.zip(fields.iterator)
.collect { case ((a, b), name) if a != b => name }
.toList
Something like this:
case class Person(fname: String, lname: String, age: Int, address: String)
case class PersonUpdate(fname: String, lname: String, age: Int)
def findFirstNameChanged(person: Person, personUpdate: PersonUpdate): List[String] =
{
if (person.fname == personUpdate.fname) List("fname")
else Nil
}
def findLastNameChanged(person: Person, personUpdate: PersonUpdate): List[String] = {
if (person.lname == personUpdate.lname) List("lname")
else Nil
}
def findAgeNameChanged(person: Person, personUpdate: PersonUpdate): List[String] = {
if (person.age == personUpdate.age) List("age")
else Nil
}
def findChangedFields(person: Person, personUpdate: PersonUpdate): Seq[String] = {
findFirstNameChanged(person,personUpdate):::
findLastNameChanged(person,personUpdate) ::: findAgeNameChanged(person,personUpdate)
}
val per = Person("Pedro","Luis",22,"street")
val personUpdate = PersonUpdate("Pedro", "Luis",27)
findChangedFields(per, personUpdate)
I think your problem is similar to compare two Set of tuple. Please feel free two correct me.
So here is my solution which will work for any two case class having field names in any order
def caseClassToSet[T](inp: T)(implicit ct: ClassTag[T]): Set[(String, AnyRef)] = {
ct.runtimeClass.getDeclaredFields.map(f => {
f.setAccessible(true)
val res = (f.getName, f.get(inp))
f.setAccessible(false)
res
}).toSet
}
val person = Person("x", "y", 10, "xy")
val personUpdate = PersonUpdate("z","y",12)
val personParams: Set[(String, AnyRef)] = caseClassToSet(person)
val personUpdateParams: Set[(String, AnyRef)] = caseClassToSet(personUpdate)
println(personUpdateParams diff personParams)
Got help from Get field names list from case class
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()
}
}
My case classes looks like this:
case class Person(personalInfo: PersonalInfo, bankInfo: BankInfo)
case class PersonalInfo(fname: String, lname: String)
case class BankInfo(atmCode: Int, creditCard: CreditCard)
case class CreditCard(number: Int, experationDate: String)
so to be able to get a person in my controller I added serealizer for person:
object PersonSerealizer {
implicit val PersonalInfoFormat: OFormat[PersonalInfo] = Json.format[PersonalInfo]
implicit val CreditCardFormat: OFormat[CreditCard] = Json.format[CreditCard]
implicit val BankInfoFormat: OFormat[BankInfo] = Json.format[BankInfo]
implicit val PersonFormat: OFormat[Person] = Json.format[Person]
}
in my controller I have a super simple action that looks like this:
i
mport serializers.PersonSerealizer._
def getBrothers(): Action[JsValue] = Action.async(parse.json) { request =>
request.body.validate[Person] match {
case JsSuccess(person, _) =>
brothersService.getBrothers(person) // this returns a List[Person]
.map(res => Future{Ok(res)})
case JsError(errors) => Future(BadRequest("Errors! " + errors.mkString))
}
}
but I get this error:
Error:(23, 81) No unapply or unapplySeq function found for class
BankInfo: / implicit val BankInfoFormat:
OFormat[BankInfo] = Json.format[BankInfo]
something is weird...from what I know it supposed to work
The order of defining your implicits seems to matter. Also, I think it is safer to use companion objects instead of defining them inside an arbitrary object.
case class PersonalInfo(fname: String, lname: String)
object PersonalInfo {
implicit val personalInfoJsonFormat = Json.format[PersonalInfo]
}
case class CreditCard(number: Int, experationDate: String)
object CreditCard {
implicit val creditCardJsonFormat = Json.format[CreditCard]
}
case class BankInfo(atmCode: Int, creditCard: CreditCard)
object BankInfo {
implicit val bankInfoJsonFormat = Json.format[BankInfo]
}
case class Person(personalInfo: PersonalInfo, bankInfo: BankInfo)
object Person {
implicit val personJsonFmt = Json.format[Person]
}
// Your action will be like this
def getBrothers() = Action.async(parse.json) { req =>
req.body.validate[Person] match {
case JsSuccess(p, _) =>
Future.successful(Ok(Json.toJson(service.getBrothers(p))))
case JsError(errors) =>
Future.successful(BadRequest(JsError.toJson(errors)))
}
}
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.