I have a dream of "dynamically instantiating case classes" -- and providing some dummy data for the fields depending on each fields type (I'll create some rules for that later)
So far I have some code which works with case classes with String; Long or Int ... and am a bit stuck on if its possible to handle embedded case classes
So I can instantiate case class RequiredAPIResponse (stringValue: String, longValue: Long, intVlaue: Int)
but not Outer; where Outer is ...
case class Inner (deep: String)
case class Outer (in : Inner)
The code is
def fill[T <: Object]()(implicit mf: ClassTag[T]) : T = {
val declaredConstructors = mf.runtimeClass.getDeclaredConstructors
if (declaredConstructors.length != 1)
Logger.error(/*T.toString + */" has " + declaredConstructors.length + " constructors --- only 1 currently supported.")
val constructor = declaredConstructors.headOption.get
val m = constructor.getParameterTypes.map(p => {
Logger.info("getName " + p.getName +" --- getCanonicalName " + p.getCanonicalName)
Logger.info(p.getCanonicalName)
p.getCanonicalName match {
case "java.lang.String" => /*"Name"->*/ val s : java.lang.String = "DEFAULT STRING"
s
case "long" => /*"Name"-> */ val l : java.lang.Long = new java.lang.Long(99)
l
case "int" => /*"Name"->*/ val i : java.lang.Integer = new java.lang.Integer(99)
i
case _ => /*"Name"->*/
So around here I am stuck!
//THIS IS MADE UP :) But I want to get the "Type" and recursively call fill
//fill[p # Type] <- not real scala code
//I can get it to work in a hard coded manner
//fill[Inner]
}
})
I feel like the last answer on Scala: How to invoke method with type parameter and manifest without knowing the type at compile time? is a starting point for an answer. So instead of using T <: Object; fill should take ClassTag or a TypeTag?
This code started from - How can I transform a Map to a case class in Scala? - which mentions (as the Lift-Framework does) I do have the liftweb source code; but so far have been unsuccessful in untangling all its secrets.
EDIT --- Based on Imm's points I've got the below code to work (some minor updates to his answer)
def fillInner(cls: Class[_]) : Object = {
val declaredConstructors = cls.getDeclaredConstructors
if (declaredConstructors.length != 1)
Logger.error(/*T.toString + */ " has " + declaredConstructors.length + " constructors --- only 1 currently supported.")
val constructor = declaredConstructors.headOption.get
val m = constructor.getParameterTypes.map(p => {
Logger.info("getName " + p.getName + " --- getCanonicalName " + p.getCanonicalName)
Logger.info(p.getCanonicalName)
p.getCanonicalName match {
case "java.lang.String" => /*"Name"->*/ val s: java.lang.String = "DEFAULT STRING"
s
case "long" => /*"Name"-> */ val l: java.lang.Long = new java.lang.Long(99)
l
case "int" => /*"Name"->*/ val i: java.lang.Integer = new java.lang.Integer(99)
i
case _ => fillInner(p)
}
})
constructor.newInstance(m: _*).asInstanceOf[Object]
}
def fill[T](implicit mf: ClassTag[T]) : T = fillInner(mf.runtimeClass).asInstanceOf[T]
Thanks, Brent
You're not actually using the ClassTag, just the Class[_], and none of this is typesafe (it's just Java reflection), so just pass the Class[_] recursively:
def fillInner(cls: Class[_]) : Any = {
val declaredConstructors = cls.getDeclaredConstructors
if (declaredConstructors.length != 1)
Logger.error(/*T.toString + */" has " + declaredConstructors.length + " constructors --- only 1 currently supported.")
val constructor = declaredConstructors.headOption.get
val m = constructor.getParameterTypes.map(p => {
Logger.info("getName " + p.getName +" --- getCanonicalName " + p.getCanonicalName)
Logger.info(p.getCanonicalName)
p.getCanonicalName match {
case "java.lang.String" => /*"Name"->*/ val s : java.lang.String = "DEFAULT STRING"
s
case "long" => /*"Name"-> */ val l : java.lang.Long = new java.lang.Long(99)
l
case "int" => /*"Name"->*/ val i : java.lang.Integer = new java.lang.Integer(99)
i
case _ => fillInner(p)
}
})
def fill[T: ClassTag]: T = fillInner(classOf[T].runtimeClass).asInstanceOf[T]
But you can probably accomplish what you want to do in a typesafe way, perhaps by using Shapeless:
trait Supplier[T] {
def supply: T
}
object Supplier[T] {
implicit val intSupplier = new Supplier[Int] {
def supply = 99
}
implicit val stringSupplier = ...
implicit val emptyHListSupplier = new Supplier[HNil] {
def supply = HNil
}
implicit def consHListSupplier[H, T <: HList](
implicit headSupplier: Supplier[H],
tailSupplier: Supplier[T]) = new Supplier[H :: T] {
def supply = headSupplier.supply :: tailSupplier.supply
}
}
Then by the magic of implicit resolution you can obtain a Supplier[(String :: HNil) :: Int :: HNil] or so on for any recursive HList of HLists that ultimately only contains values for which you've got Suppliers; you just need a little more shapeless (different in version 1 or 2, and it's been a while since I've done it, so I don't remember the specifics) to convert back and forth between those and case classes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With