case class 到 shapeless 通用转换

case class to shapeless Generic conversion

问题:

我有一个案例 class Foo 可以是任何东西。

我需要一个函数来创建密码查询。

这个新函数的签名应该是

def createQueryString[T](t: T): String = ???

示例:

我有Foo假设有两个成员

case class Foo(x: Int, y: String)

我需要将其转换为密码

CREATE (f:Foo { x: "1", y: "Hello" }) RETURN f

如果我将Foo(1, "Hello")传递给上面提到的函数createQueryString

createQueryString[Foo](Foo(1, "Hello"))

到目前为止我尝试了什么?

我已经尝试使用 shapelessGenericAux 来实现这个

import shapeless._

case class Foo(x: Int, y: String)

def foo[T, HL <: HList](instance: T)(
  implicit gen: Generic.Aux[T, HL]
): HL = {
  gen.to(instance)
}

val myFoo = foo(Foo(1, "Hello"))
s"""CREATE (f:Foo { x: "${myFoo(0)}", y: "${myFoo(1)}" }) RETURN f"""

有什么办法可以用这个foo(Foo(1, "Hello"))在上面提到的createQueryString里面实现吗?我想基本上将类型传递给这个函数。有点像

def createQueryString[T](t: T): String = {
  val gen = foo(t) // to get the generic
  s"""CREATE (t: T { x: "${gen(0)}", y: "${gen(1)}" }) RETURN t"""
}

像这样。但是通过这样做我得到以下错误

Error:(77, 22) could not find implicit value for parameter gen: shapeless.Generic.Aux[T,HL]
val gen = foo(t) // to get the generic

问题:

  1. 我在上次使用 Aux 的实现中遗漏了什么?
  2. 我怎样才能改进它使其足够通用以作为参数合并到密码查询中?在 class.
  3. 的情况下,可以有多个 xy

您忘记了隐式参数(或上下文边界)。

Generic不仅可以产生HList,还可以产生Coproduct。这就是为什么如果你松散绑定 <: HList 编译器不知道如何将 GenericRepr 应用到 01.

尝试

import shapeless.ops.hlist.At
import shapeless.nat._

def foo[T](instance: T)(
  implicit gen: Generic[T]
): gen.Repr = {
  gen.to(instance)
}

def createQueryString[T, L <: HList](t: T)(implicit 
  g: Generic.Aux[T, L],
  at0: At[L, _0], 
  at1: At[L, _1]
): String = {
  val gen = foo(t) // to get the generic
  s"""CREATE (t: T { x: "${gen(0)}", y: "${gen(1)}" }) RETURN t"""
}

如果您需要标签 xy,那么您需要 LabelledGeneric 而不是 Generic

如果您有任意数量的参数,那么您可以将 HList 转换为所需的形式,然后将其折叠为字符串。

{ x: "1", y: "Hello" } 看起来像 JSON。看看Circe.

这可能不是您正在寻找的答案,但对于您给出的示例,使用 Reflection:

def createQueryString[T](t: T): String = {

  def formatFields() = {
  t.getClass().getDeclaredFields().filterNot(_.getName.startsWith("(")).map(f => {
    f.setAccessible(true)
    f.getName + ": \"" + f.get(t).toString + "\""
  }).mkString(", ")
}

val className = t.getClass.getSimpleName
val paramName = nameOf(t)
s"""CREATE ($paramName:$className { $formatFields }) RETURN $paramName"""

}

输出:

CREATE (t:Foo { x: "1", y: "Hello" }) RETURN t

"(" 过滤掉构造函数,只给出 vars/vals。 可以定制此代码以通过递归管理嵌套 类。

依赖关系:

反射支持:

"org.scala-lang" % "scala-reflect" % scalaVersion.value

为了方便获取参数名:

"com.github.dwickern" %% "scala-nameof" % "4.0.0" % "provided"

附带说明一下,这可能是使用反射的罕见好用例之一,因为任何硬编码值都不会改变或访问任何内容。