Get field names list from case class
Asked Answered
L

9

67

I need to get only field names of case class. I'm not interested in its values. I thought getClass.getDeclaredFields.map(_.getName) would return a list of field names.

scala> case class User(id: Int, name: String)
defined class User

scala> User.getClass.getDeclaredFields
res14: Array[java.lang.reflect.Field] = Array(public static final User$ User$.MODULE$)

scala> User.getClass.getDeclaredFields.toList
res15: List[java.lang.reflect.Field] = List(public static final User$ User$.MODULE$)

scala> val user = User(1, "dude")
user: User = User(1,dude)

scala> user.getClass.getDeclaredFields.toList
res16: List[java.lang.reflect.Field] = List(private final int User.id, private final java.lang.String User.name)

What is this User$.MODULE$? What's that?

Method getDeclaredFields works fine when you have an instance of a case class, but I don't want to create an instance in order to get only fields.

Why this isn't true: User.getClass.getDeclaredFields.map(_.getName) == List("id", "name")?

Lumpen answered 2/7, 2015 at 16:19 Comment(0)
A
77

By using User.getClass, you are referring to the class companion object that Scala by default creates for the case class, and not the case class itself. To get the class object of the case class, use classOf[User].

Alternatively, you could use Scala's reflection API to get the metadata of a case class, which gives you much more information:

import scala.reflect.runtime.universe._

def classAccessors[T: TypeTag]: List[MethodSymbol] = typeOf[T].members.collect {
  case m: MethodSymbol if m.isCaseAccessor => m
}.toList

Test in sbt console:

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

scala> classAccessors[User]
res0: List[reflect.runtime.universe.MethodSymbol] = List(value age, value name)
Archilochus answered 2/7, 2015 at 16:36 Comment(3)
They appear to come back in lexo order. Is there any way to make them come back in the order they are declared in the code?En
This answer has a thread which explains how to get in order: https://mcmap.net/q/296912/-scala-2-10-reflection-how-do-i-extract-the-field-values-from-a-case-class-i-e-field-list-from-case-classEn
This answer would be better if it contained how to get exactly the field names - it's unclear how to go from a MethodSymbol to a string name, e.g. "name".Skaggs
F
49

Starting Scala 2.13, case classes (which are an implementation of Product) are now provided with a productElementNames method which returns an iterator over their field's names.

From an instance of the case class (let's say case class Person(name: String, age: Int)), one can retrieve a List of its fields:

Person("hello", 28).productElementNames.toList
// List[String] = List(name, age)
Forkey answered 2/10, 2018 at 0:15 Comment(3)
That's great! oh i'm on spark .. 2.13 is in spark 4 that will be 3 years outParrotfish
The frustrating thing with approach is that you need an instance of the type in question, which is not quite ideal in certain situationsBastion
Also, it's runtime evaluated to a List, which makes it less useful, when trying to work with Tuple-based logic - despite the size of the list being determined at compile time.Liatris
H
15

Following Andrey Tyukin solution, to get only the list of fields in Scala 2.12:

val fields: List[String] = classOf[Dummy].getDeclaredFields.map(_.getName).toList
Heraldic answered 9/7, 2020 at 11:22 Comment(0)
F
11

User.getClass does not give you the equivalent of User.class in Java, but it gives you the class of the companion object of the User class. You can retrieve the Class object of User with classOf[User].

edit: Oh and the User$.MODULE$ is an accessor to the singleton instance that is used internally. Think of it as the equivalent to MyClass.INSTANCE when you are writing singletons in Java.

Floristic answered 2/7, 2015 at 16:26 Comment(0)
S
10

If you are also wondering why some Scala-reflection code is not compiling any more, here is a crude solution with the good ol' Java reflection (which, apparently, has been working in exactly the same way since approx. year 1300 BC):

case class User(b: Int, a: String)
val u = User(42, "JohnDoe")

classOf[User]
.getDeclaredFields
.map{ f => 
  f.setAccessible(true)
  val res = (f.getName, f.get(u))
  f.setAccessible(false)
  res
}

It will get both the names and the values:

Array((b,42), (a,bob))

The order seems to be the same as in the constructor.

Subdeacon answered 20/12, 2018 at 15:38 Comment(2)
This approach is good, but beware if you have any member fields not in the default constructor, .getDeclaredFields will return those as well and not differentiate between the twoRunkle
@Andrey Tyukin, how to use it in a generic method def getFields[T]() : List[Field] = ???Ranite
A
3

If you are using Spark, this the easiest way to get fields:

val cols = Seq(CaseClassModel()).toDF().columns

Note: The CaseClassModel should have fields initialized

Arietta answered 21/10, 2019 at 23:43 Comment(1)
Will you please add a bit more to your code @Devendra ParhateRanite
L
3

Extracting case class field names with Shapeless

LabelledGeneric Aux pattern enter image description here

https://svejcar.dev/posts/2019/10/22/extracting-case-class-field-names-with-shapeless/

"Scala 2.13 added new method, productElementNames, into the Product trait"

... but the Shapeless based approach is recommended by author

Lathi answered 13/7, 2021 at 15:53 Comment(1)
in comparision to using 'scala.reflect.runtime.universe._` approach, can you please explain the good/bad about this shapeless approach?Hydrogenous
N
0

In Scala 3, meta programming constructs provides us with a more Scala idiomatic solution:

import scala.deriving.Mirror
import scala.compiletime.constValueTuple
case class User(id: Int, name: String)
val userMirror = summon[Mirror.Of[User]]
constValueTuple[userMirror.MirroredElemLabels].toList.asInstanceOf[List[String]]

Nessa answered 8/3 at 18:12 Comment(0)
O
-2
import scala.reflect.runtime.universe._

object CaseClassUtils {

  def productElementNames[T <: Product: TypeTag]: Set[String] =
    typeOf[T].members.collect {
      case m: MethodSymbol if m.isCaseAccessor => m.name.toString
    }.toSet

}
Occam answered 28/3 at 12:43 Comment(1)
This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation?Marcelina

© 2022 - 2024 — McMap. All rights reserved.