You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 21:30:18