List of strings to case class with inner case classes - scala

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

Related

How to convert values of a case class into Seq?

I am new to Scala and I am having to provide values extracted from an object/case class into a Seq. I was wondering whether there would be any generic way of extracting values of an object into Seq of those values in order?
Convert the following:
case class Customer(name: Option[String], age: Int)
val customer = Customer(Some("John"), 24)
into:
val values = Seq("John", 24)
case class extends Product class and it provides such method:
case class Person(age:Int, name:String, lastName:Option[String])
def seq(p:Product) = p.productIterator.toList
val s:Seq[Any] = seq(Person(100, "Albert", Some("Einstain")))
println(s) //List(100, Albert, Some(Einstain))
https://scalafiddle.io/sf/oD7qk8u/0
Problem is that you will get untyped list/array from it. Most of the time it is not optimal way of doing things, and you should always prefer statically typed solutions.
Scala 3 (Dotty) might give us HList out-of-the-box which is a way of getting product's values without loosing type information. Given val picard = Customer(Some("Picard"), 75) consider the difference between
val l: List[Any] = picard.productIterator.toList
l(1)
// val res0: Any = 75
and
val hl: (Option[String], Int) = Tuple.fromProductTyped(picard)
hl(1)
// val res1: Int = 75
Note how res1 did not loose type information.
Informally, it might help to think of an HList as making a case class more generic by dropping its name whilst retaining its fields, for example, whilst Person and Robot are two separate models
Robot(name: Option[String], age: Int)
Person(name: Option[String], age: Int)
they could both represented by a common "HList" that looks something like
(_: Option[String], _: Int) // I dropped the names
If it's enough for you to have Seq[Any] you can use productIterator approach proposed by #Scalway. If I understood correctly you want also to unpack Option fields. But you haven't specified what to do with None case like Customer(None, 24).
val values: Seq[Any] = customer.productIterator.map {
case Some(x) => x
case x => x
}.toSeq // List(John, 24)
Statically typed solution would be to use heterogeneous collection e.g. HList
class Default[A](val value: A)
object Default {
implicit val int: Default[Int] = new Default(0)
implicit val string: Default[String] = new Default("")
//...
}
trait LowPriorityUnpackOption extends Poly1 {
implicit def default[A]: Case.Aux[A, A] = at(identity)
}
object unpackOption extends LowPriorityUnpackOption {
implicit def option[A](implicit default: Default[A]): Case.Aux[Option[A], A] = at {
case Some(a) => a
case None => default.value
}
}
val values: String :: Int :: HNil =
Generic[Customer].to(customer).map(unpackOption) // John :: 24 :: HNil
Generally it would be better to work with Option monadically rather than to unpack them.

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

Is there a name for the pattern of converting underlying representations to case classes?

Assume a basic case class like
case class Natural(i: Int)
Which is
class Natural(val i: Int)
object Natural {
def apply(i: Int): Natural = new Natural(i)
def unapply(n: Natural): Option[Int] = Some(n.i)
}
but I also have
def xxxx(i: Int): Option[Natural] = if (i > 0) Some(Natural(i)) else None
that is similar to unapply, but with reversed parameters (like apply). Another example:
class HttpMethod(val name: String)
object HttpMethod {
def apply(name: String): HttpMethod = new HttpMethod(name)
def unapply(method: HttpMethod): Option[String] = Some(method.name)
def xxxx(name: String): Option[HttpMethod] = name.toUpperCase match {
case "GET" => Some(HttpMethod("GET"))
case "PUT" => Some(HttpMethod("PUT"))
...
case _ => None
}
}
Is there any convention for the name of xxxx ? I thought from, but it could also be an overloaded unapply. If it were the latter you could do
val methodString: String = ???
methodString match {
case m # HttpMethod(name) => // use m...
}
(if it works, could fail because of overloading). But note that this isn't an extractor, but like a constructor, because it says: if this string can be an Http method, then build it and ... The real extractor is
val method: HttpMethod = ???
method match {
case HttpMethod(name) => // and now you have extracted the string name.
}
Of course it'd make more sense in a GADT with sealed trait, but the question isn't about this, but about the convention for xxxx

Dispatch on return type in Scala

Let's say I have the following case classes:
case class Category(name: String)
case class Record(price: Double, description: String, category: Category)
case class Sale(price: Double, qty: Int, dateOfSale: Date, category: Category)
I want to invoke a method that returns a list of Sale or Record based on category. The only difference is the return type. So rather than findSalesByCategory and findRecordsByCategory I want something like:
def findByCategory[T](category: Category): List[T] = classOf[T] match {
case c if c == classOf[Sale] => findRecordsByCategory(category)
case c if c == classOf[Record] => findSalesByCategory(category)
}
Obviously this fails compilation with:
error: class type required but T found
def findByCategory[T](category: Category): List[T] = classOf[T] match {
^
Is there an approach that works?
You can use typetags:
import scala.reflect.runtime.universe._
def findByCategory[T: TypeTag](category: Category): List[T] = {
typeOf[T] match {
case t if t =:= typeOf[Sale] => findSalesByCategory(category)
case t if t =:= typeOf[Record] => findRecordsByCategory(category)
}
}
Because your categories are not generic, ClassTag will also suffice:
import scala.reflect.{ClassTag, classTag}
def findByCategory[T: ClassTag](category: Category): List[T] = {
classTag[T] match {
case c if c == classTag[Sale] => findSalesByCategory(category)
case c if c == classTag[Record] => findRecordByCategory(category)
}
}
This will work when subtypes of T you're matching on are not parameterized; in that case you'll have to use TypeTags.
There is a way to simplify matching a bit. You can save ClassTags or Classes into a val and match on it:
import scala.reflect.{ClassTag, classTag}
val saleClass = classOf[Sale]
val recordClass = classOf[Record]
def findByCategory[T: ClassTag](category: Category): List[T] = {
classTag[T].runtimeClass match {
case `saleClass` => findSalesByCategory(category)
case `recordClass` => findRecordByCategory(category)
}
}

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