How to collect field values from case classes in scala - scala

I've got various case classes with different fields inherit some trait. All are mixed in a List. What is the way to collect (or group by) specific field's values?
sealed trait Template
object Template {
case class TemplateA(field: String) extends Template
case class TemplateB extends Template
}
object Runner {
def main(args: String*) {
val list = List(TemplateA("abc"), TemplateB, Template("cde"))
// need to output something like "abc;1", "cde;1"
}
}

Totally agree with #LuisMiguel, just to show one way of doing this, here's what I can think of:
trait Template { val field: Option[String] }
case class TemplateA(field: Option[String]) extends Template
case class TemplateB() extends Template { override val field: Option[String] = None }
val list: List[Template] = List(
TemplateA(Some("abc")),
TemplateB(),
TemplateA(Some("cde"))
)
list.collect {
case template if template.field.nonEmpty =>
template.field.get
}.groupMapReduce(identity)(_ => 1)(_ + _)
// res8: Map[String, Int] = Map("abc" -> 1, "cde" -> 1)
Or if you want to get rid of the Optional argument when instantiating TemplateA instances, you can also do this:
case class TemplateA(private val value: String) extends Template {
override val field: Option[String] = Option(value)
}
val list: List[Template] = List(TemplateA("abc"), TemplateB(), TemplateA("cde"))
As #DmytroMitin mentioned, we can do a bit of refactoring to avoid using ifs in our collect function, I'd rather use some sort of unapply function, that can extract the field value of TemplateA instances:
object Template { // or any name as you wish
def unapply(t: Template): Option[String] = t match {
case TemplateA(Some(value)) => Option(value)
case _ => None
}
}
And then, we can use pattern matching:
list.collect {
case Template(field) => field
}.groupMapReduce(identity)(_ => 1)(_ + _)

Related

compare case class fields with sub fields of another case class in scala

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()
}
}

Scala assign type a dynamic value

I'm pretty new to Scala and I'm wondering if it is possible to create a type dynamically in some way. In practice what I want to achieve is something like this:
trait BaseAB
case class A(value: String) extends BaseAB
case class B(value: String) extends BaseAB
def build(name: String, m: String): BaseAB = {
type t = name match {
case "A" => A
base "B" => B
}
new t(m)
}
You can just create new instances in you case clauses, like
case "A" => A(m)
case "B" => B(m)
or you can create partially applied function representing constructor and then provide value
def build(name: String, m: String): BaseAB = {
val construct = name match {
case "A" => A.apply _
case "B" => B.apply _
}
construct(m)
}
> build("A", "boo")
res25: BaseAB = A("boo")
Your code works almost as-is, but it's not because there is some sort of "type-valued runtime-defined variables". Instead, it works because there are companion objects called A and B that have methods apply(s: String): A and apply(s: String): B, and also both conform to type String => BaseAB:
trait BaseAB
case class A(value: String) extends BaseAB
case class B(value: String) extends BaseAB
def build(name: String, m: String): BaseAB = {
val t = name match {
case "A" => A
case "B" => B
}
t(m)
}
In this code snippet, the type of t is inferred to be String => BaseAB (possibly with some additional marker traits like Serializable).
If you are sure that there are only "A" and "B", you can also write it as
(if (name == "A") A else B)(m)
it works for the same reason.

List of strings to case class with inner case classes

Let's say i have 2 cases classes:
case class Money(amount: Int, currency: String)
case class Human(name: String, money: Money)
is there a nice way to "translate" a list of strings to class Human? smth like:
def superMethod[A](params: List[String]): A = ???
val params: List[Any] = List("john", 100, "dollar")
superMethod(params) // => Human("john", Money(100, "dollar"))
so essentially i know type A only in runtime
UPDATE: i found ~ what i was looking for. it seems i can do it via shapeless. example i found in github
Here is an implementation that works for generic classes A.
It relies on runtime reflection (that is, a different TypeTag can be passed to the method at runtime). The following obvious conditions must be fulfilled in order to use this method:
A must be on the class path, or otherwise be loadable by the used class loader
TypeTag must be available for A at the call site.
The actual implementation is in the Deserializer object. Then comes a little demo.
The deserializer:
import scala.reflect.runtime.universe.{TypeTag, Type}
object Deserializer {
/** Extracts an instance of type `A` from the
* flattened `Any` constructor arguments, and returns
* the constructed instance together with the remaining
* unused arguments.
*/
private def deserializeRecHelper(
flattened: List[Any],
tpe: Type
): (Any, List[Any]) = {
import scala.reflect.runtime.{universe => ru}
// println("Trying to deserialize " + tpe + " from " + flattened)
// println("Constructor alternatives: ")
// val constructorAlternatives = tpe.
// member(ru.termNames.CONSTRUCTOR).
// asTerm.
// alternatives.foreach(println)
val consSymb = tpe.
member(ru.termNames.CONSTRUCTOR).
asTerm.
alternatives(0).
asMethod
val argsTypes: List[Type] = consSymb.paramLists(0).map(_.typeSignature)
if (tpe =:= ru.typeOf[String] || argsTypes.isEmpty) {
val h :: t = flattened
(h, t)
} else {
val args_rems: List[(Any, List[Any])] = argsTypes.scanLeft(
(("throwaway-sentinel-in-deserializeRecHelper": Any), flattened)
) {
case ((_, remFs), t) =>
deserializeRecHelper(remFs, t)
}.tail
val remaining: List[Any] = args_rems.last._2
val args: List[Any] = args_rems.unzip._1
val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
val classMirror = runtimeMirror.reflectClass(tpe.typeSymbol.asClass)
val cons = classMirror.reflectConstructor(consSymb)
// println("Build constructor arguments array for " + tpe + " : " + args)
val obj = cons.apply(args:_*)
(obj, remaining)
}
}
def deserialize[A: TypeTag](flattened: List[Any]): A = {
val (a, rem) = deserializeRecHelper(
flattened,
(implicitly: TypeTag[A]).tpe
)
require(
rem.isEmpty,
"Superfluous arguments remained after deserialization: " + rem
)
a.asInstanceOf[A]
}
}
Demo:
case class Person(id: String, money: Money, pet: Pet, lifeMotto: String)
case class Money(num: Int, currency: String)
case class Pet(color: String, species: Species)
case class Species(description: String, name: String)
object Example {
def main(args: Array[String]): Unit = {
val data = List("Bob", 42, "USD", "pink", "invisible", "unicorn", "what's going on ey?")
val p = Deserializer.deserialize[Person](data)
println(p)
}
}
Output:
Person(Bob,Money(42,USD),Pet(pink,Species(invisible,unicorn)),what's going on ey?)
Discussion
This implementation is not restricted to case classes, but it requires each "Tree-node-like" class to have exactly one constructor that accepts either
primitive types (Int, Float), or
strings, or
other "Tree-node-like" classes.
Note that the task is somewhat ill-posed: what does it mean to say that all constructor arguments are flattened in a single list? Given the class Person(name: String, age: Int), will the List[Any] contain every single byte of the name as a separate entry? Probably not. Therefore, strings are handled by the deserializer in a special way, and all other collection-like entities are not supported for the same reasons (unclear where to stop parsing, because size of the collection is not known).
In case A is not a generic type, but effectively Human, you can use a companion object to the case class Human:
object Human {
def fromList(list: List[String]): Human = list match {
case List(name, amount, currency) => Human(name, Money(amount.toInt, currency))
case _ => handle corner case
}
}
Which you can call:
Human.fromList(List("john", "100", "dollar"))
To make it safe, don't forget to handle the case of lists whose size wouldn't be 3; and of lists whose 2nd element can't be cast to an Int:
import scala.util.Try
object Human {
def fromList(list: List[String]): Option[Human] = list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount.toInt, currency))).toOption
case _ => None
}
}
Edit: Based on your last comment, you might find this usefull:
case class Money(amount: Int, currency: String)
case class Human(name: String, money: Money)
case class SomethingElse(whatever: Double)
object Mapper {
def superMethod(list: List[String]): Option[Any] =
list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount.toInt, currency))).toOption
case List(whatever) => Try(SomethingElse(whatever.toDouble)).toOption
case _ => None
}
}
println(Mapper.superMethod(List("john", 100, "dollar")))
> Some(Human(john,Money(100,dollar)))
println(Mapper.superMethod(List(17d)))
> Some(SomethingElse(17.0))
or alternatively:
object Mapper {
def superMethod[A](list: List[String]): Option[A] =
(list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount, currency))).toOption
case List(whatever) =>
Try(SomethingElse(whatever.toDouble)).toOption
case _ => None
}).map(_.asInstanceOf[A])
}
println(Mapper.superMethod[Human](List("john", "100", "dollar")))
> Some(Human(john,Money(100,dollar)))
println(Mapper.superMethod[SomethingElse](List("17.2")))
> Some(SomethingElse(17.0))

case class product iterator to make string

I want to make a string representation of case class
case class Person(s:Student)
case class Student(name:String,value:String){
def toMyString(flag:Boolean):String= if(flag)s"${name} =${value}" else s"Hello${name}+${value}"
}
Person(Student("abc","def")).productIterator
I want to call toMyString from productIterator.
My actual use case has many elements in case class .
Just ask each iterator element if it is the target type.
Person(Student("abc","def")).productIterator.collect{
case x:Student => x.toMyString(true)
}
// res0: Iterator[String] = non-empty iterator (String: "abc=def")
There are 2 solutions for generating the String for the element:
1.Create a trait for elements that need to implement toMyString and the elements that need to generate string extends from it, and implement it, like:
trait MyString {
def toMyString(flag: Boolean): String
}
case class Student(name: String, value: String) extends MyString{
def toMyString(flag: Boolean): String = if (flag) s"${name} =${value}" else s"Hello${name}+${value}"
}
val result: List[String] = Person(Student("abc","def")).productIterator.collect{
case x: MyString => x.toMyString(true)
}
2. Use the reflection for this by get toMyString method by method name, this is a tricky way and not type safe, should think more about this, like:
val student = Student("abc", "def")
val res: Iterator[String] = Person(student).productIterator.map(i => {
val method = i.getClass.getDeclaredMethods.filter(i => i.getName.equals("toMyString")).headOption
method match {
case Some(m) =>
m.invoke(i, true.asInstanceOf[AnyRef])
case None =>
null
}
})
We need Iterator[Student] instead of Iterator[Any]
Person(Student("abc","def")).productIterator.asInstanceOf[Iterator[Student]]

Scala 2.10 reflection, how do I extract the field values from a case class, i.e. field list from case class

How can I extract the field values from a case class in scala using the new reflection model in scala 2.10?
For example, using the below doesn't pull out the field methods
def getMethods[T:TypeTag](t:T) = typeOf[T].members.collect {
case m:MethodSymbol => m
}
I plan to pump them into
for {field <- fields} {
currentMirror.reflect(caseClass).reflectField(field).get
}
MethodSymbol has an isCaseAccessor method that allows you to do precisely this:
def getMethods[T: TypeTag] = typeOf[T].members.collect {
case m: MethodSymbol if m.isCaseAccessor => m
}.toList
Now you can write the following:
scala> case class Person(name: String, age: Int)
defined class Person
scala> getMethods[Person]
res1: List[reflect.runtime.universe.MethodSymbol] = List(value age, value name)
And you get only the method symbols you want.
If you just want the actual field name (not the value prefix) and you want them in the same order then:
def getMethods[T: TypeTag]: List[String] =
typeOf[T].members.sorted.collect {
case m: MethodSymbol if m.isCaseAccessor => m.name.toString
}
If you want to get fancier you can get them in order by inspecting the constructor symbol. This code works even if the case class type in question has multiple constructors defined.
import scala.collection.immutable.ListMap
import scala.reflect.runtime.universe._
/**
* Returns a map from formal parameter names to types, containing one
* mapping for each constructor argument. The resulting map (a ListMap)
* preserves the order of the primary constructor's parameter list.
*/
def caseClassParamsOf[T: TypeTag]: ListMap[String, Type] = {
val tpe = typeOf[T]
val constructorSymbol = tpe.decl(termNames.CONSTRUCTOR)
val defaultConstructor =
if (constructorSymbol.isMethod) constructorSymbol.asMethod
else {
val ctors = constructorSymbol.asTerm.alternatives
ctors.map(_.asMethod).find(_.isPrimaryConstructor).get
}
ListMap[String, Type]() ++ defaultConstructor.paramLists.reduceLeft(_ ++ _).map {
sym => sym.name.toString -> tpe.member(sym.name).asMethod.returnType
}
}