Reputation: 2453
Having case class with nested Options how do I convert it to its twin case class with no Options by extracting Some
’s and replacing None
’s by default values.
Let's say we have:
case class UserPreferencesOpt(animals: Option[AnimalPreferencesOpt])
case class AnimalPreferencesOpt(dogs: Option[String], cats: Option[String])
case class UserPreferences(animals: AnimalPreferences)
object UserPreferences {
def getDefault: UserPreferences(AnimalPreferences("bark", "meow")
}
case class AnimalPreferences(dogs: String, cats: String)
So basically, I’d like to convert:
UserPreferencesOpt(Some(AnimalPreferencesOpt(Some("Dogs are cool!"), None)))
into:
UserPreferences(AnimalPreferences("Dogs are cool!", "meow")
by extracting Options when present and replacing absent with default value.
It can be done by pattern matching and checking value by value, but this is just an example. Obviously, in many cases, there will be much more nested preferences in domain logic and I wonder if it’s possible to convert it without adding extra matches and cases when new preferences are added (eg. WeatherPreferences
or even something inside UserPreferences).
Having heard of some advanced Scala stuff like Shapeless or macros I think somebody might recommend it, but is there a way to achieve it in idiomatic, functional, Scala-ish way?
Upvotes: 4
Views: 1521
Reputation: 628
There is a library supports this feature, like this:
import cats.data.Validated
import cats.implicits._
import henkan.optional.all._
case class Message(a: Option[String], b: Option[Int])
case class Domain(a: String, b: Int)
validate(Message(Some("a"), Some(2))).to[Domain]
// res0: henkan.optional.ValidateFromOptional.Result[Domain] = Valid(Domain(a,2))
validate(Message(Some("a"), None)).to[Domain]
// res1: henkan.optional.ValidateFromOptional.Result[Domain] = Invalid(NonEmptyList(RequiredFieldMissing(b)))
Upvotes: 1
Reputation: 3055
Another scala-ish way to solve this problem is using implicit conversions
. You have to define implicit methods
that convert toConvert class to required class e.g. UserPreferenceOpt
to UserPreference
in their respective companion classes.
case class UserPreferencesOpt(animals: Option[AnimalPreferencesOpt])
case class AnimalPreferencesOpt(dogs: Option[String], cats: Option[String])
case class UserPreferences(animals: AnimalPreferences)
case class AnimalPreferences(dogs: String, cats: String)
//Note that, I have used `null` value in case of `None` found in Opt class. Instead of `null`, you can provide default value with some logic here.
object UserPreferencesOpt {
implicit def optToUserPref(userPref: UserPreferencesOpt): UserPreferences = UserPreferences(userPref.animals.getOrElse(null))
}
object AnimalPreferencesOpt {
implicit def optToAnimalPref(animalPref: AnimalPreferencesOpt): AnimalPreferences = AnimalPreferences(animalPref.dogs.getOrElse(null), animalPref.cats.getOrElse(null))
}
val userPrefOpt:UserPreferencesOpt = UserPreferencesOpt(Some(AnimalPreferencesOpt(Some("Dogs are cool!"), None)))
val userPref: UserPreferences = userPrefOpt
Upvotes: 1
Reputation: 15539
If the default are static, then the simpler solution is to fill it at the instance construction, and not having class with Options.
For your example, that would be:
case class UserPreferences(animals: AnimalPreferences)
case class AnimalPreferences(dogs: String, cats: String)
object UserPreferences {
def apply(dogOpt: Option[String], catOpt: Option[String]): UserPreferences = {
val dog = dogOpt.getOrElse("bark")
val cat = catOpt.getOrElse("meow")
UserPreferences(AnimalPreferences(dog, cat))
}
}
// then you can create, eg:
UserPreferences(None, None)
UserPreferences("Grrr", None)
UserPreferences("Dogs are cool!", "meow")
Upvotes: 1
Reputation: 1770
You can specify defaults in apply
in companion object for target case classes. E.g.:
case class UserPreferencesOpt(animals: Option[AnimalPreferencesOpt] = None)
case class AnimalPreferencesOpt(dogs: Option[String] = None, cats: Option[String] = None)
case class UserPreferences(animals: AnimalPreferences)
object UserPreferences {
def apply(o: UserPreferencesOpt): UserPreferences = {
UserPreferences(
AnimalPreferences(
o.animals.getOrElse(AnimalPreferencesOpt())
))
}
}
case class AnimalPreferences(dogs: String, cats: String)
object AnimalPreferences {
def apply(opt: AnimalPreferencesOpt): AnimalPreferences = {
AnimalPreferences(
opt.dogs.getOrElse("bark"),
opt.cats.getOrElse("meow")
)
}
}
But I agree with @pedrorijo91 that model looks weird..
Upvotes: 1