0

I have a case class with Options:

case class PersonUpdate(name: Option[String], age: Option[Int], country: Option[String])

and I need to check which values are defined and generate a map with its name and values, for example:

if I have this object:

val perUpdate = PersonUpdate(Option("john"), None, Option("England"))

than the map should look like:

val result = Map("people.$.name" -> "john", "people.$.country" -> "England")

what would be the best way do that efficiently the scala way?

Antot
  • 3,904
  • 1
  • 21
  • 27
JohnBigs
  • 2,691
  • 3
  • 31
  • 61
  • Really you should be using `Some("john")` rather than `Option("john")` if you're going for best practices. An `Option` should be either a `Some(x)` or `None`. – James Whiteley Jul 18 '18 at 08:29
  • https://stackoverflow.com/questions/1226555/case-class-to-map-in-scala see this answer of how to transform case class into map. – vvg Jul 18 '18 at 08:32
  • @VolodymyrGlushak in this link it dosent only take the fields that are defined.. – JohnBigs Jul 18 '18 at 09:20

2 Answers2

0

For your specific case you can do this:

List(
  perUpdate.name.map("people.$.name" -> _),
  perUpdate.age.map("people.$.age" -> _.toString),
  perUpdate.country.map("people.$.country" -> _)
).flatten.toMap

You can have a more generic solution, but it is not going to be particularly efficient:

perUpdate.getClass.getDeclaredFields.flatMap { f =>
  f.setAccessible(true)

  f.get(perUpdate).asInstanceOf[Option[Any]].map("people.$."+f.getName -> _.toString)
}.toMap

To extract only certain fields, try this:

val fieldNames = List("name", "age", "country")

fieldNames.flatMap{ fieldName =>
  val fieldValue = perUpdate.getClass.getDeclaredField(fieldName)
  fieldValue.setAccessible(true)

  fieldValue.get(perUpdate).asInstanceOf[Option[Any]].map("people.$."+fieldName -> _.toString)
}.toMap
Tim
  • 26,753
  • 2
  • 16
  • 29
  • Why the anonymous downvote? Please explain why this is a bad answer so we can all learn something. – Tim Jul 19 '18 at 11:17
  • It was a revenge from me ;) Reflection is always a bad answer, when there is a solution that does not use it. I did not want to downvote earlier (it's an ok solution, just not a very good one), but after the hostility you have shown earlier towards my, better, answer, I changed my mind. – Dima Jul 19 '18 at 15:52
  • @Dima What is your objection to reflection? Seems like a valid option in this case. – Tim Jul 19 '18 at 16:51
  • The general objection is that it is slow like a snail. It also breaks api contracts, and looses type safety. Basically, it defeats the whole purpose of writing your code in scala entirely. Could as well just use javascript (where maps and "case classes" are already the same thing - problem solved). – Dima Jul 19 '18 at 17:00
0

Case classes are instances of Product, that lets you iterate through their members without reflection:

Seq("name", "age", "country")
  .map { "people.$." + _ }
  .iterator
  .zip(perUpdate.productIterator)
  .collect { case (k, Some(v)) => k -> v }
  .toMap
Dima
  • 39,570
  • 6
  • 44
  • 70
  • Compiles now. Sorry for confusion - I just meant it as an illustration, didn't bother to iron out the details. – Dima Jul 19 '18 at 11:05
  • This still does not produce the right output, 3rd time lucky? Also, this solution is flakey because it relies on the programmer keeping the `Seq` of field names in sync with the case class (name, number, and order). In a large system with multiple programmers this is hard to achieve, and if you get it wrong the compiler and runtime won't help you out. Better to go with explicit extraction if you care about robustness and performance, or reflection if you care about generality. – Tim Jul 19 '18 at 16:35
  • Fixed the output now (for the record, once again, it was never my goal, to provide the complete working solution - just an illustration of the approach). I submit, that converting case classes to maps isn't a very good idea either way, especially in "large systems with multiple programmers", regardless of the approach. – Dima Jul 19 '18 at 16:55