Instantiating a case class with default args via reflection

后端 未结 4 1471
孤街浪徒
孤街浪徒 2021-01-24 04:03

I need to be able to instantiate various case classes through reflection, both by figuring out the argument types of the constructor, as well as invoking the constructor with al

4条回答
  •  失恋的感觉
    2021-01-24 04:46

    Here's a working version that you can copy into your codebase:

    import scala.reflect.api
    import scala.reflect.api.{TypeCreator, Universe}
    import scala.reflect.runtime.universe._
    
    object Maker {
      val mirror = runtimeMirror(getClass.getClassLoader)
    
      var makerRunNumber = 1
    
      def apply[T: TypeTag]: T = {
        val method = typeOf[T].companion.decl(TermName("apply")).asMethod
        val params = method.paramLists.head
        val args = params.map { param =>
          makerRunNumber += 1
          param.info match {
            case t if t <:< typeOf[Enumeration#Value] => chooseEnumValue(convert(t).asInstanceOf[TypeTag[_ <: Enumeration]])
            case t if t =:= typeOf[Int] => makerRunNumber
            case t if t =:= typeOf[Long] => makerRunNumber
            case t if t =:= typeOf[Date] => new Date(Time.now.inMillis)
            case t if t <:< typeOf[Option[_]] => None
            case t if t =:= typeOf[String] && param.name.decodedName.toString.toLowerCase.contains("email") => s"random-$arbitrary@give.asia"
            case t if t =:= typeOf[String] => s"arbitrary-$makerRunNumber"
            case t if t =:= typeOf[Boolean] => false
            case t if t <:< typeOf[Seq[_]] => List.empty
            case t if t <:< typeOf[Map[_, _]] => Map.empty
            // Add more special cases here.
            case t if isCaseClass(t) => apply(convert(t))
            case t => throw new Exception(s"Maker doesn't support generating $t")
          }
        }
    
        val obj = mirror.reflectModule(typeOf[T].typeSymbol.companion.asModule).instance
        mirror.reflect(obj).reflectMethod(method)(args:_*).asInstanceOf[T]
      }
    
      def chooseEnumValue[E <: Enumeration: TypeTag]: E#Value = {
        val parentType = typeOf[E].asInstanceOf[TypeRef].pre
        val valuesMethod = parentType.baseType(typeOf[Enumeration].typeSymbol).decl(TermName("values")).asMethod
        val obj = mirror.reflectModule(parentType.termSymbol.asModule).instance
    
        mirror.reflect(obj).reflectMethod(valuesMethod)().asInstanceOf[E#ValueSet].head
      }
    
      def convert(tpe: Type): TypeTag[_] = {
        TypeTag.apply(
          runtimeMirror(getClass.getClassLoader),
          new TypeCreator {
            override def apply[U <: Universe with Singleton](m: api.Mirror[U]) = {
              tpe.asInstanceOf[U # Type]
            }
          }
        )
      }
    
      def isCaseClass(t: Type) = {
        t.companion.decls.exists(_.name.decodedName.toString == "apply") &&
          t.decls.exists(_.name.decodedName.toString == "copy")
      }
    }
    

    And, when you want to use it, you can call:

    val user = Maker[User]
    val user2 = Maker[User].copy(email = "someemail@email.com")
    

    The code above generates arbitrary and unique values. The data aren't exactly randomised. It's best for using in tests.

    It works with Enum and nested case class. You can also easily extend it to support some other special types.

    Read our full blog post here: https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html

提交回复
热议问题