Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala 2.10: Dynamically instantiating case classes

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

like image 543
brent Avatar asked Aug 11 '26 06:08

brent


1 Answers

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.

like image 197
lmm Avatar answered Aug 14 '26 00:08

lmm



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!