如何通过字符串定义使用Shapeless Lenses访问Scala样例类字段?
用Shapeless通过动态字符串访问Scala样例类字段
首先明确说:可以实现,但分两种场景——编译时已知字段名,和运行时动态传入字段名,咱们分别来看解决方案:
为什么你的代码会报错?
你尝试用fieldName.witness时出错,核心原因是Shapeless的MkFieldLens需要编译时确定的字段标签(Symbol类型),而普通字符串变量的witness只能提供String类型的见证,不是Shapeless字段需要的Symbol类型;更关键的是,如果字段名是运行时才确定的变量,编译时根本没法生成对应的Lens隐式实例,自然找不到mkLens。
场景1:编译时已知字段名(常量字符串)
如果你的字段名是编译时就能确定的(比如硬编码的字符串常量),可以把字符串转成Symbol类型的见证,就能复用Lens的逻辑:
import shapeless._ import shapeless.ops.hlist._ import shapeless.lens._ case class Test(id: String, calc: Long) val instance = Test("123232", 3434L) // 字段名是编译时常量 val fieldName = "id" // 转成Symbol并生成见证 val fieldSymbol = Symbol(fieldName) implicit val fieldWitness: Witness.Aux[fieldSymbol.type] = Witness(fieldSymbol) // 现在就能正常创建Lens了 val lens = lens[Test] >> fieldSymbol val valueOfFieldId = lens.get(instance) // 结果: "123232"
场景2:运行时动态传入字段名
如果字段名是运行时才确定的(比如来自用户输入、配置文件),就不能直接用Lens了,得借助Shapeless的LabelledGeneric把样例类转成带字段标签的HList,再遍历查找匹配的字段:
第一步:定义通用的字段获取工具
import shapeless._ import shapeless.labelled._ // 定义递归遍历HList的类型类 trait FieldGetter[L <: HList] { def get(hlist: L, targetField: String): Option[Any] } object FieldGetter { // 空HList的处理逻辑 implicit val hnilGetter: FieldGetter[HNil] = (_: HNil, _: String) => None // 非空HList的处理逻辑:匹配当前字段,不匹配就递归找尾部 implicit def hconsGetter[K <: Symbol, V, T <: HList]( implicit fieldWitness: Witness.Aux[K], tailGetter: FieldGetter[T] ): FieldGetter[FieldType[K, V] :: T] = { (hlist: FieldType[K, V] :: T, targetField: String) => if (fieldWitness.value.name == targetField) Some(hlist.head) else tailGetter.get(hlist.tail, targetField) } } // 对外暴露的工具方法 def getDynamicField[A, Repr <: HList](instance: A, fieldName: String)( implicit gen: LabelledGeneric.Aux[A, Repr], getter: FieldGetter[Repr] ): Option[Any] = { getter.get(gen.to(instance), fieldName) }
第二步:测试使用
case class Test(id: String, calc: Long) val instance = Test("123232", 3434L) // 获取存在的字段 val idValue = getDynamicField(instance, "id") // Some("123232") val calcValue = getDynamicField(instance, "calc") // Some(3434) // 获取不存在的字段返回None val invalidValue = getDynamicField(instance, "name") // None
这个方法返回Option[Any],如果需要类型安全的返回值,可以在获取后手动做类型转换(比如.asInstanceOf[String]),或者进一步扩展工具类支持类型参数。
内容的提问来源于stack exchange,提问作者sbb
相关产品推荐
相关产品推荐

