Reputation: 1849
Problem:
I have a case class Foo
which can be anything.
I need a function to create a cypher query from this.
The signature of this new function should be
def createQueryString[T](t: T): String = ???
Example:
I have Foo
let's say having two members
case class Foo(x: Int, y: String)
I need it to be converted to cypher
CREATE (f:Foo { x: "1", y: "Hello" }) RETURN f
If I pass Foo(1, "Hello")
into the function createQueryString
mentioned above
createQueryString[Foo](Foo(1, "Hello"))
What have I tried so far?
I have tried using shapeless's Generic
and Aux
to achieve this
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"""
Is there any way I can use this foo(Foo(1, "Hello"))
to implement inside the createQueryString
mentioned above? I would want to basically pass types into this function down the road. Somewhat like
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"""
}
Something like this. But by doing this I get the following error
Error:(77, 22) could not find implicit value for parameter gen: shapeless.Generic.Aux[T,HL]
val gen = foo(t) // to get the generic
Questions:
Aux
?x
and y
in the case class.Upvotes: 3
Views: 373
Reputation: 1000
This may not be the answer you are looking for but for your given example, using 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"""
}
Output:
CREATE (t:Foo { x: "1", y: "Hello" }) RETURN t
"(" fileters out the constructer and only gives vars/vals. This code can be tailored to manage nested classes through recursion.
Depndencies:
For reflection support:
"org.scala-lang" % "scala-reflect" % scalaVersion.value
For conveniently getting param name:
"com.github.dwickern" %% "scala-nameof" % "4.0.0" % "provided"
On a side note though, this could be one of rare good usecases for using reflection since nothing is being mutated or accessed by any hardcoded value.
Upvotes: 0
Reputation: 51648
You forgot implicit parameters (or context bounds).
Generic
can produce not only an HList
but also a Coproduct
. That's why if you loose bound <: HList
compiler doesn't know how to apply Generic
's Repr
to 0
, 1
.
Try
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"""
}
If you need labels x
, y
then you need LabelledGeneric
rather than Generic
.
If you have arbitrary number of parameters then you can transform an HList to desired form and then fold it to a string.
{ x: "1", y: "Hello" }
looks like a JSON. Look at Circe.
Upvotes: 4