Scala 动态创建案例类

Scala 动态创建案例类,scala,slick,scala-macros,Scala,Slick,Scala Macros,我正在使用一个csv库,它接受一个案例类,并将其转换成行供我阅读 语法非常接近文件(path).asCsvReader[caseClass]。 链接到图书馆 然而,问题是我想从数据库中的表生成case类。我可以接收数据库中的表和列的类型(Int、Long、Double、String等),但我不知道如何使用这些数据动态创建case类,因为我在编译时不知道这些信息 正因为如此,我也不能使用宏,因为我在宏编译时不知道表数据 那么,一旦收到表数据,我将如何动态创建这个case类,然后将该case类传递到

我正在使用一个csv库,它接受一个案例类,并将其转换成行供我阅读

语法非常接近
文件(path).asCsvReader[caseClass]
。 链接到图书馆

然而,问题是我想从数据库中的表生成case类。我可以接收数据库中的表和列的类型(Int、Long、Double、String等),但我不知道如何使用这些数据动态创建case类,因为我在编译时不知道这些信息

正因为如此,我也不能使用宏,因为我在宏编译时不知道表数据


那么,一旦收到表数据,我将如何动态创建这个case类,然后将该case类传递到csv库?

那么基本上您需要一个关于case类的运行时信息?我想您应该使用
ClassTag

import scala.reflect._

def asCsvReader[T: ClassTag]: T = {
  classTag[T].runtimeClass.getDeclaredConstructor(...).newInstance(...)
  ...
}
这将允许您在运行时实例化case类


由于您可以确定CSV列的类型,因此可以将适当的类型提供给
getDeclaredConstructor
newInstance
方法。

如果您使用的是Scala 2.10,则可以使用包中的类来完成此操作。请注意,这在Scala 2.11中不再有效。我问了一个问题,希望我们能得到答案

在Scala2.10中,使用解释器可以编译外部类文件并虚拟加载它

这些步骤是:

  • 根据约定为类指定一个名称
  • 解析CSV标题以了解字段名称和数据类型
  • 用上述信息生成一个case类并写入磁盘上的文件
  • 加载生成的源文件并使用解释器编译它们
  • 类现在可以使用了
我已经建立了一个小演示,应该会有所帮助

/**
  * Location to store temporary scala source files with generated case classes
  */
val classLocation: String = "/tmp/dynacode"

/**
  * Package name to store the case classes
  */
val dynaPackage: String = "com.example.dynacsv"

/**
  * Construct this data based on your data model e.g. see data type for Person and Address below.
  * Notice the format of header, it can be substituted directly in a case class definition.
  */
val personCsv: String = "PersonData.csv"
val personHeader: String = "title: String, firstName: String, lastName: String, age: Int, height: Int, gender: Int"

val addressCsv: String = "AddressData.csv"
val addressHeader: String = "street1: String, street2: String, city: String, state: String, zipcode: String"

/**
  * Utility method to extract class name from CSV file
  * @param filename CSV file
  * @return Class name extracted from csv file name stripping out ".ext"
  */
def getClassName(filename: String): String = filename.split("\\.")(0)

/**
  * Generate a case class and persist to file
  * @param file External file to write to
  * @param className Class name
  * @param header case class parameters
  */
def writeCaseClass(file: File, className: String, header: String): Unit = {
  val writer: PrintWriter = new PrintWriter(file)
  writer.println("package " + dynaPackage)
  writer.println("case class " + className + "(")
  writer.println(header)
  writer.println(") {}")
  writer.flush()
  writer.close()
}

/**
  * Generate case class and write to file
  * @param filename CSV File name (should be named ClassName.csv)
  * @param header Case class parameters. Format is comma separated: name: DataType
  * @throws IOException if there is problem writing the file
  */
@throws[IOException]
private def generateClass(filename: String, header: String) {
  val className: String = getClassName(filename)
  val fileDir: String = classLocation + File.separator + dynaPackage.replace('.', File.separatorChar)
  new File(fileDir).mkdirs
  val classFile: String = fileDir + File.separator + className + ".scala"
  val file: File = new File(classFile)

  writeCaseClass(file, className, header)
}

/**
  * Helper method to search code files in directory
  * @param dir Directory to search in
  * @return
  */
def recursiveListFiles(dir: File): Array[File] = {
  val these = dir.listFiles
  these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}

/**
  * Compile scala files and keep them loaded in memory
  * @param classDir Directory storing the generated scala files
  * @throws IOException if there is problem reading the source files
  * @return Classloader that contains the compiled external classes
  */
@throws[IOException]
def compileFiles(classDir: String): AbstractFileClassLoader = {
  val files = recursiveListFiles(new File(classDir))
                  .filter(_.getName.endsWith("scala"))
  println("Loaded files: \n" + files.mkString("[", ",\n", "]"))

  val settings: GenericRunnerSettings = new GenericRunnerSettings(err => println("Interpretor error: " + err))
  settings.usejavacp.value = true
  val interpreter: IMain = new IMain(settings)
  files.foreach(f => {
    interpreter.compileSources(new BatchSourceFile(AbstractFile.getFile(f)))
  })

  interpreter.getInterpreterClassLoader()
}

//Test Address class
def testAddress(classLoader: AbstractFileClassLoader) = {
  val addressClass = classLoader.findClass(dynaPackage + "." + getClassName(addressCsv))
  val ctor = addressClass.getDeclaredConstructors()(0)
  val instance = ctor.newInstance("123 abc str", "apt 1", "Hello world", "HW", "12345")
  println("Instantiated class: " + instance.getClass.getCanonicalName)
  println(instance.toString)
}

//Test person class
def testPerson(classLoader: AbstractFileClassLoader) = {
  val personClass = classLoader.findClass(dynaPackage + "." + getClassName(personCsv))
  val ctor = personClass.getDeclaredConstructors()(0)
  val instance = ctor.newInstance("Mr", "John", "Doe", 25: java.lang.Integer, 165: java.lang.Integer, 1: java.lang.Integer)
  println("Instantiated class: " + instance.getClass.getCanonicalName)
  println(instance.toString)
}

//Test generated classes
def testClasses(classLoader: AbstractFileClassLoader) = {
  testAddress(classLoader)
  testPerson(classLoader)
}

//Main method
def main(args: Array[String]) {
  try {
    generateClass(personCsv, personHeader)
    generateClass(addressCsv, addressHeader)
    val classLoader = compileFiles(classLocation)
    testClasses(classLoader)
  }
  catch {
    case e: Exception => e.printStackTrace()
  }
}
}

输出:

Loaded files: 
[/tmp/dynacode/com/example/dynacsv/AddressData.scala,
 /tmp/dynacode/com/example/dynacsv/PersonData.scala]
Instantiated class: com.example.dynacsv.AddressData
AddressData(123 abc str,apt 1,Hello world,HW,12345)
Instantiated class: com.example.dynacsv.PersonData
PersonData(Mr,John,Doe,25,165,1)
根据中的提示,我将添加第二个解决方案,该解决方案可以在Scala 2.10和2.11中使用,并使用Scala。生成的cases类位于默认包中

生成案例类 使用工具箱编译外部类 实例化并使用编译类 编译后的类可以从映射中获得,并在必要时使用,例如

//Test Address class
def testAddress(map: Map[String, Class[_]]) = {
  val addressClass = map("AddressData")
  val ctor = addressClass.getDeclaredConstructors()(0)
  val instance = ctor.newInstance("123 abc str", "apt 1", "Hello world", "HW", "12345")
  //println("Instantiated class: " + instance.getClass.getName)
  println(instance.toString)
}

您如何想象在不知道这些case类是什么样子的情况下操作程序中的这些case类?一旦你回答了这个问题,你就知道了你想要使用的结构的形状。我将只访问传递给它们的变量。例如,case class blah(val s:String)我将只访问s变量。在这种情况下,使用不同的库(或同一库中的不同方法)来提供CSV记录的通用表示(例如元组或无形状的
HList
)可能会更容易。很抱歉,我不太确定这是怎么回事。你能解释一下吗?对不起,也许我把你的问题搞错了。是否要生成特定案例类的实例?还是案例类本身?如果您询问有关case类生成的问题,那么这是不可能的,因为Scala是一种静态类型语言。您不能在运行时生成新成员(或Python中的属性)。但是,如果您想在运行时创建由泛型T表示的某个特定案例类的实例,我的建议将适用于您。我基本上希望生成一个不存在的案例类。您好,我试图遵循您的方法,但我面临异常。我写了一篇关于这个问题的新文章,以防你有时间。我知道这个答案已经3岁半了。但如果你能告诉我我做错了什么,我将不胜感激。
/**
  * Helper method to search code files in directory
  * @param dir Directory to search in
  * @return
  */
def recursiveListFiles(dir: File): Array[File] = {
  val these = dir.listFiles
  these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}  

/**
  * Compile scala files and keep them loaded in memory
  * @param classDir Directory storing the generated scala files
  * @throws IOException if there is problem reading the source files
  * @return Map containing Class name -> Compiled Class Reference
  */
@throws[IOException]
def compileFiles(classDir: String): Map[String, Class[_]] = {
  val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()

  val files = recursiveListFiles(new File(classDir))
    .filter(_.getName.endsWith("scala"))
  println("Loaded files: \n" + files.mkString("[", ",\n", "]"))

  files.map(f => {
    val src = Source.fromFile(f).mkString
    val clazz = tb.compile(tb.parse(src))().asInstanceOf[Class[_]]
    getClassName(f.getName) -> clazz
  }).toMap
}
//Test Address class
def testAddress(map: Map[String, Class[_]]) = {
  val addressClass = map("AddressData")
  val ctor = addressClass.getDeclaredConstructors()(0)
  val instance = ctor.newInstance("123 abc str", "apt 1", "Hello world", "HW", "12345")
  //println("Instantiated class: " + instance.getClass.getName)
  println(instance.toString)
}