I am looking at getting case class definitions.
From SO I gleaned this practice as per Get field names list from case class, the answer using reflection by Dia Kharrat.
Some experimenting in which I have a case class referring to another case class, nested. Can we get the metadata expanded easily in some way?
import scala.collection.mutable.ArrayBuffer
case class MyChgClass(b: Option[String], c: Option[String], d: Option[String])
case class MyFullClass(k: Int, b: String, c: String, d: String)
case class MyEndClass(id: Int, after: MyFullClass)
def classAccessors[T: TypeTag]: List[MethodSymbol] = typeOf[T].members.collect {
case m: MethodSymbol if m.isCaseAccessor => m
}.toList
val z1 = classAccessors[MyChgClass]
val z2 = classAccessors[MyFullClass]
val z3 = classAccessors[MyEndClass]
returns:
z1: List[reflect.runtime.universe.MethodSymbol] = List(value d, value c, value b)
z2: List[reflect.runtime.universe.MethodSymbol] = List(value d, value c, value b, value k)
z3: List[reflect.runtime.universe.MethodSymbol] = List(value after, value id)
So:
- Looking to expand the case class MyEndClass.
- The option aspect appears not not been supplied. Possible?
- The option aspect appears not not been supplied. Possible?
Are you looking for .name
and .typeSignature
?
val z1 = classAccessors[MyChgClass]
val z2 = classAccessors[MyFullClass]
val z3 = classAccessors[MyEndClass]
z1.map(_.name) // List(d, c, b)
z1.map(_.typeSignature) // List(Option[String], Option[String], Option[String])
z2.map(_.name) // List(d, c, b, k)
z2.map(_.typeSignature) // List(String, String, String, Int)
z3.map(_.name) // List(after, id)
z3.map(_.typeSignature) // List(MyFullClass, Int)
If your classes are known at compile time it would make sense to use compile-time reflection i.e. macros rather than runtime reflection
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def classAccessors[T]: List[(String, String)] = macro classAccessorsImpl[T]
def classAccessorsImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val pairs = weakTypeOf[T].members.collect {
case m: MethodSymbol if m.isCaseAccessor => m
}.map(m => (m.name.toString, m.typeSignature.toString))
q"List.apply[(String, String)](..$pairs)"
}
// in a different subproject
classAccessors[MyChgClass] // List((d,Option[String]), (c,Option[String]), (b,Option[String]))
classAccessors[MyFullClass] // List((d,String), (c,String), (b,String), (k,Int))
classAccessors[MyEndClass] // List((after,MyFullClass), (id,Int))
Even better would be to use one of libraries encapsulating those macros into some type classes. In Shapeless the type class giving access to names and types of case-class fields is LabelledGeneric
// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.labelled.FieldType
import shapeless.ops.hlist.{FillWith, Mapper, ToList}
import shapeless.{HList, LabelledGeneric, Poly0, Poly1, Typeable, Witness}
object fieldNamesAndTypesPoly 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 classAccessors[T] = new PartiallyApplied[T]
class PartiallyApplied[T] {
def apply[L <: HList, L1 <: HList]()(implicit
labelledGeneric: LabelledGeneric.Aux[T, L],
fillWith: FillWith[nullPoly.type, L],
mapper: Mapper.Aux[fieldNamesAndTypesPoly.type, L, L1],
toList: ToList[L1, (String, String)]
): List[(String, String)] = toList(mapper(fillWith()))
}
classAccessors[MyChgClass]() // List((b,Option[String]), (c,Option[String]), (d,Option[String]))
classAccessors[MyFullClass]() // List((k,Int), (b,String), (c,String), (d,String))
classAccessors[MyEndClass]() // List((id,Int), (after,MyFullClass))
- Looking to expand the case class
MyEndClass
.
You can try deep versions of the type classes LabelledGeneric
, Mapper
etc.
import shapeless.labelled.{FieldType, field}
import shapeless.{::, DepFn0, DepFn1, HList, HNil, LabelledGeneric, Poly0, Poly1, Typeable, Witness, poly}
trait DeepLabelledGeneric[T <: Product] {
type Repr <: HList
def to(t: T): Repr
def from(r: Repr): T
}
object DeepLabelledGeneric {
type Aux[T <: Product, Repr0 <: HList] = DeepLabelledGeneric[T] {type Repr = Repr0}
def instance[T <: Product, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepLabelledGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def deepGeneric[A <: Product, L <: HList, L1 <: HList](implicit
labelledGeneric: LabelledGeneric.Aux[A, L],
hListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[L, L1]
): Aux[A, L1] = instance(a => hListDeepLabelledGeneric.to(labelledGeneric.to(a)), l1 => labelledGeneric.from(hListDeepLabelledGeneric.from(l1)))
}
trait HListDeepLabelledGeneric[T <: HList] {
type Repr <: HList
def to(t: T): Repr
def from(r: Repr): T
}
trait LowPriorityHListDeepLabelledGeneric {
type Aux[T <: HList, Repr0 <: HList] = HListDeepLabelledGeneric[T] {type Repr = Repr0}
def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepLabelledGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def headNotCaseClass[H, T <: HList, T_hListDeepLGen <: HList](implicit
tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
): Aux[H :: T, H :: T_hListDeepLGen] = instance({
case h :: t => h :: tailHListDeepLabelledGeneric.to(t)
}, {
case h :: t => h :: tailHListDeepLabelledGeneric.from(t)
})
}
object HListDeepLabelledGeneric extends LowPriorityHListDeepLabelledGeneric {
implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)
implicit def headCaseClass[K <: Symbol, H <: Product, T <: HList, H_deepLGen <: HList, T_hListDeepLGen <: HList](implicit
headDeepLabelledGeneric: DeepLabelledGeneric.Aux[H, H_deepLGen],
tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
): Aux[FieldType[K, H] :: T, FieldType[K, H_deepLGen] :: T_hListDeepLGen] = instance({
case h :: t => field[K](headDeepLabelledGeneric.to(h)) :: tailHListDeepLabelledGeneric.to(t)
}, {
case h :: t => field[K](headDeepLabelledGeneric.from(h)) :: tailHListDeepLabelledGeneric.from(t)
})
}
trait DeepMapper[P <: Poly1, In <: HList] extends DepFn1[In] {
type Out <: HList
}
trait LowPriorityDeepMapper {
def apply[P <: Poly1, L <: HList](implicit deepMapper: DeepMapper[P, L]): Aux[P, L, deepMapper.Out] = deepMapper
type Aux[P <: Poly1, In <: HList, Out0 <: HList] = DeepMapper[P, In] {type Out = Out0}
def instance[P <: Poly1, In <: HList, Out0 <: HList](f: In => Out0): Aux[P, In, Out0] = new DeepMapper[P, In] {
override type Out = Out0
override def apply(t: In): Out = f(t)
}
implicit def headNotHList[P <: Poly1, H, T <: HList](implicit
headCase: poly.Case1[P, H],
tailDeepMapper: DeepMapper[P, T]
): Aux[P, H :: T, headCase.Result :: tailDeepMapper.Out] =
instance(l => headCase(l.head) :: tailDeepMapper(l.tail))
}
object DeepMapper extends LowPriorityDeepMapper {
implicit def hNil[P <: Poly1]: Aux[P, HNil, HNil] = instance(_ => HNil)
implicit def headHList[P <: Poly1, K <: Symbol, H <: HList, H_deepMap <: HList, T <: HList](implicit
headDeepMapper: DeepMapper.Aux[P, H, H_deepMap],
headCase: poly.Case1[P, FieldType[K, H_deepMap]], // apply poly one more time
tailDeepMapper: DeepMapper[P, T]
): Aux[P, FieldType[K, H] :: T, headCase.Result :: tailDeepMapper.Out] =
instance(l => headCase(field[K](headDeepMapper(l.head))) :: tailDeepMapper(l.tail))
}
trait DeepFillWith[P <: Poly0, L <: HList] extends DepFn0 {
type Out = L
}
trait LowPriorityDeepFillWith {
def apply[P <: Poly0, L <: HList](implicit deepFillWith: DeepFillWith[P, L]): DeepFillWith[P, L] = deepFillWith
def instance[P <: Poly0, L <: HList](f: => L): DeepFillWith[P, L] = new DeepFillWith[P, L] {
override def apply(): L = f
}
implicit def headNotHList[P <: Poly0, H, T <: HList](implicit
headCase: poly.Case0.Aux[P, H],
tailDeepFillWith: DeepFillWith[P, T]
): DeepFillWith[P, H :: T] =
instance(headCase() :: tailDeepFillWith())
}
object DeepFillWith extends LowPriorityDeepFillWith {
implicit def hNil[P <: Poly0]: DeepFillWith[P, HNil] = instance(HNil)
implicit def headHList[P <: Poly0, K <: Symbol, H <: HList, T <: HList](implicit
headDeepFillWith: DeepFillWith[P, H],
tailDeepFillWith: DeepFillWith[P, T]
): DeepFillWith[P, FieldType[K, H] :: T] =
instance(field[K](headDeepFillWith()) :: tailDeepFillWith())
}
trait LowPriorityFieldNamesAndTypesPoly extends Poly1 {
implicit def notHListCase[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 fieldNamesAndTypesPoly extends LowPriorityFieldNamesAndTypesPoly {
implicit def hListCase[K <: Symbol, V <: HList](implicit
witness: Witness.Aux[K],
): Case.Aux[FieldType[K, V], (String, V)] =
at(v => (witness.value.name, v)) // for DeepMapper "applying this poly one more time"
}
object nullPoly extends Poly0 {
implicit def cse[A]: Case0[A] = at(null.asInstanceOf[A])
}
def classAccessors[T <: Product] = new PartiallyApplied[T]
class PartiallyApplied[T <: Product] {
def apply[L <: HList]()(implicit
deepLabelledGeneric: DeepLabelledGeneric.Aux[T, L],
deepFillWith: DeepFillWith[nullPoly.type, L],
deepMapper: DeepMapper[fieldNamesAndTypesPoly.type, L],
): deepMapper.Out = deepMapper(deepFillWith())
}
classAccessors[MyChgClass]() // (b,Option[String]) :: (c,Option[String]) :: (d,Option[String]) :: HNil
classAccessors[MyFullClass]() // (k,Int) :: (b,String) :: (c,String) :: (d,String) :: HNil
classAccessors[MyEndClass]() // (id,Int) :: (after,(k,Int) :: (b,String) :: (c,String) :: (d,String) :: HNil) :: HNil
Deriving nested shapeless lenses using only a type
Weird behavior trying to convert case classes to heterogeneous lists recursively with Shapeless
Converting nested case classes to nested Maps using Shapeless
Automatically convert a case class to an extensible record in shapeless?