3

In Scala, given a generic type T, how to retrieve the list of field names and the field types? For example, if I have the case class:

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

And the generic function:

def getFieldNamesAndTypes[T](): Seq[(String, String)]

I would like to be able to retrieve a sequence (in order that the fields appear) of the fields (name, type):

val fieldNamesAndTypes = getFieldNamesAndTypes[Person]()
code
  • 5,294
  • 16
  • 62
  • 113
  • 3
    Looks like X/Y. What is the goal for which you think getting such information would be a way to implement a solution (and which solution)? BTW, runtime reflection should only be considered in edge cases, as it defeats all the type safety. – cchantep Aug 07 '20 at 09:48
  • 1
    I see a few questions about class and types information that you have posted recently. I propose you to learn shapeless/magnolia libraries. All your questions would be covered. – Artem Sokolov Aug 07 '20 at 11:15

2 Answers2

4

It seems that you need the reflection API:

Welcome to Scala 2.13.1 (OpenJDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> def getFields[T: TypeTag] = typeOf[T].members.collect {
     |   case m: MethodSymbol if m.isCaseAccessor => (m.name.toString, m.returnType.toString)
     | }
getFields: [T](implicit evidence$1: reflect.runtime.universe.TypeTag[T])Iterable[(String, String)]

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

scala> getFields[Person]
res0: Iterable[(String, String)] = List((gender,Boolean), (age,Int), (name,String))
esse
  • 1,415
  • 5
  • 10
4

You can do this even at compile time using Shapeless (i.e. using compile-time reflection under the hood)

import shapeless.labelled.FieldType
import shapeless.ops.hlist.{FillWith, Mapper, ToList}
import shapeless.{HList, LabelledGeneric, Poly0, Poly1, Typeable, Witness}

object fieldTypePoly extends Poly1 {
  implicit def cse[K <: Symbol, V](implicit
    witness: Witness.Aux[K],
    typeable: Typeable[V]
  ): Case.Aux[FieldType[K, V], (String, String)] =
    at(_ => witness.value.name -> typeable.describe)
}

object nullPoly extends Poly0 {
  implicit def cse[A]: Case0[A] = at(null.asInstanceOf[A])
}

def getFieldNamesAndTypes[T] = new PartiallyApplied[T]

class PartiallyApplied[T] {
  def apply[L <: HList, L1 <: HList]()(implicit
    generic: LabelledGeneric.Aux[T, L],
    mapper: Mapper.Aux[fieldTypePoly.type, L, L1],
    fillWith: FillWith[nullPoly.type, L],
    toList: ToList[L1, (String, String)]
  ): Seq[(String, String)] = toList(mapper(fillWith()))
}

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

getFieldNamesAndTypes[Person]() // List((name,String), (age,Int), (gender,Boolean))
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66