基于Shapeless生成样例类的递归类型树(无需实例化)
如何基于Scala样例类生成类型树(无需实例化)
要实现从Scala样例类生成递归类型树且不依赖对象实例,用Shapeless的LabelledGeneric配合递归类型类是最直接的方案——它能在编译期提取样例类的字段名和类型信息,完美契合需求。以下是完整实现:
1. 引入依赖
首先在sbt中添加Shapeless依赖:
libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
2. 实现类型树生成逻辑
定义一个TypeTree类型类,递归处理基本类型和样例类:
import shapeless._ import shapeless.labelled.FieldType import scala.reflect.runtime.universe.TypeTag // 用Map存储类型树结构,便于后续转JSON或其他格式 trait TypeTree[T] { def generate: Map[String, Any] } object TypeTree { // 提取类型的短名称(去掉包路径) private def typeName[T](implicit tt: TypeTag[T]): String = tt.tpe.toString.split("\\.").last // 基本类型的处理逻辑:直接返回类型名称 implicit def primitiveTypeTree[T: TypeTag]: TypeTree[T] = () => Map("_type" -> typeName[T]) // HList空列表的递归终止条件 implicit val hnilTypeTree: TypeTree[HNil] = () => Map.empty // 递归处理带标签的HList(对应样例类的字段列表) implicit def hconsTypeTree[K <: Symbol, H, T <: HList]( implicit witness: Witness.Aux[K], // 获取字段名 hTree: Lazy[TypeTree[H]], // Lazy处理递归类型依赖 tTree: TypeTree[T] ): TypeTree[FieldType[K, H] :: T] = () => { val fieldName = witness.value.name val fieldTree = hTree.value.generate val tailTree = tTree.generate tailTree + (fieldName -> fieldTree) } // 样例类的核心处理:通过LabelledGeneric转成带标签HList再递归 implicit def caseClassTypeTree[C, L <: HList]( implicit gen: LabelledGeneric.Aux[C, L], // 把样例类映射为带字段标签的HList lTree: Lazy[TypeTree[L]], tt: TypeTag[C] ): TypeTree[C] = () => { val fieldsTree = lTree.value.generate fieldsTree + ("_type" -> typeName[C]) } } // 便捷调用方法 def getTypeTree[T](implicit tree: TypeTree[T]): Map[String, Any] = tree.generate
3. 测试示例
用你提供的样例类测试:
case class C1(x: Int) case class C2(s: String, b: Boolean) case class C3(c1: C1, c2: C2) // 注:匹配你给出的类型树结构,修正了原样例类的c2类型 // 生成类型树 val c3TypeTree = getTypeTree[C3] println(c3TypeTree)
输出结果(格式化后):
Map( "_type" -> "C3", "c1" -> Map("_type" -> "C1", "x" -> Map("_type" -> "Int")), "c2" -> Map("_type" -> "C2", "s" -> Map("_type" -> "String"), "b" -> Map("_type" -> "Boolean")) )
4. 转JSON(可选)
如果需要转成JSON格式,可借助Circe等JSON库:
import io.circe.syntax._ import io.circe.generic.auto._ println(c3TypeTree.asJson)
关键说明
LabelledGeneric是核心:它将样例类转换为带字段名标签的HList,让我们能在编译期获取字段名和对应类型Lazy用于处理递归类型:比如样例类互相引用的场景,避免编译期死循环- 若想用
Typeable替代TypeTag获取类型名称,只需把typeName方法替换为Typeable[T].describe即可
内容的提问来源于stack exchange,提问作者Markus1189
相关产品推荐
相关产品推荐

