How To Access access Case class field Value from String name of the field
Asked Answered
I

3

11

How should I extract the value of a field of a case class from a given String value representing the field.

For example:

case class Person(name: String, age: Int)
val a = Person("test",10)

Now here given a string name or age i want to extract the value from variable a. How do i do this? I know this can be done using reflection but I am not exactly sure how?

Innkeeper answered 7/8, 2017 at 13:15 Comment(3)
And i want to be able to extract the value safely. Meaning that if the field is not there i should get None or something.Innkeeper
In what use case might a Person not be expected to have a name or age?Lejeune
In the use case where the system does not have complete information.Melodeemelodeon
P
9

What you're looking for can be achieve using Shapeless lenses. This will also put the constraint that a field actually exists on a case class at compile time rather than run time:

import shapeless._

case class Person(name: String, age: Int)

val nameLens = lens[Person] >> 'name
val p = Person("myName", 25)

nameLens.get(p)

Yields:

res0: String = myName

If you try to extract a non existing field, you get a compile time error, which is a much stronger guarantee:

import shapeless._

case class Person(name: String, age: Int)

val nonExistingLens = lens[Person] >> 'bla
val p = Person("myName", 25)

nonExistingLens.get(p)

Compiler yells:

Error:(5, 44) could not find implicit value for parameter mkLens: shapeless.MkFieldLens[Person,Symbol with shapeless.tag.Tagged[String("bla")]]
val nonExistingLens = lens[Person] >> 'bla
Pertain answered 7/8, 2017 at 13:51 Comment(4)
Could we use String instead of Symbol like 'bla ?Discern
@Discern Hi, did you ever find out a way to use a String variable instead of a Symbol like 'blaStambaugh
@Stambaugh You can't just use any arbitrary String variable since the Symbol needs to be known at compile time.Pertain
Aah, I see. Thank you.Stambaugh
B
1

don't know exactly what you had in mind, but a match statement would do, it is not very generic or extensible with regards changes to the Person case class, but it does meet your basic requirements of not using reflection:

scala> val a = Person("test",10)
a: Person = Person(test,10)

scala> def extract(p: Person, fieldName: String) = {
     |   fieldName match {
     |     case "name" => p.name
     |     case "age" => p.age
     |   }
     | }
extract: (p: Person, fieldName: String)Any

scala> extract(a, "name")
res1: Any = test

scala> extract(a, "age")
res2: Any = 10

scala> extract(a, "name####")
scala.MatchError: name#### (of class java.lang.String)
  at .extract(<console>:14)
  ... 32 elided

UPDATE as per comment:

scala> case class Person(name: String, age: Int)
defined class Person

scala> val a = Person("test",10)
a: Person = Person(test,10)


scala> def extract(p: Person, fieldName: String) = {
     |   fieldName match {
     |     case "name" => Some(p.name)
     |     case "age" => Some(p.age)
     |     case _ => None
     |   }
     | }
extract: (p: Person, fieldName: String)Option[Any]

scala> extract(a, "name")
res4: Option[Any] = Some(test)

scala> extract(a, "age")
res5: Option[Any] = Some(10)

scala> extract(a, "name####")
res6: Option[Any] = None

scala>
Bloodyminded answered 7/8, 2017 at 13:35 Comment(2)
Ya but then i need to write a wrapper. I want to achieve this at runtime without manually writing a wrapper function.Innkeeper
then perhaps checkout shapeless: shapeless lensesBloodyminded
K
0

I think it can do by convert case class to Map, then get field by name

def ccToMap(cc: AnyRef) =
  (Map[String, Any]() /: cc.getClass.getDeclaredFields) {
     (a, f) =>
     f.setAccessible(true)
     a + (f.getName -> f.get(cc))
}

Usage

case class Person(name: String, age: Int)

val column = Person("me", 16)
println(ccToMap(column))
val name = ccToMap(column)["name"]
Katushka answered 27/1, 2019 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.