从案例类中获取变量名

问题描述 投票:0回答:1

:)

我想在案例类中引用变量名称(只是名称,没有值)。

接下来是一个非常简化的示例:

case class Person(name: String, age: Int)
val schema = Encoders.products[Person].schema
val jack = Person("name", 20)

override def method[Person](df: DataFrame) : DataFrame = {        
  df.withColumn("json", from_json(col("column_value"), schema))
    .select("json.*")
    .withColumn(jack.name, trim(col(jack.name)))
    .withColumn(jack.age, col(jack.age) + 2)
}

当然,jack.name将返回该值,它是一个字符串,对我的配偶很有效。但正如您已经可以想象的那样,jack.age将给我值,没有“年龄”。

到目前为止,我明白了,我认为这是一个非常丑陋且效率低下的解决方案:

val onlyNames: Seq[String] = schema.map(_.name) 
...
.withColumn(...)
.withColumn(onlyNames(2), col(onlyNames(2)) + 2)

版本:Spark 2.3.0 // Scala 2.11.8

scala apache-spark-sql case-class
1个回答
0
投票

在Scala 2.13中,您可以使用:

val person = Person("John", 23)
(person.productEleementNames zip person.productIterator).foldLeft(dataFrame) {
  case (dataFrame, (name, value)) =>
    dataFrame.withColumn(name, value) // example
}

但是由于Spark,您处于2.11或至多处于2.12,因此您必须使用其他方式。

一种方法是使用运行时反射:

(person.getClass.getDeclaredFields.map(_.getName) zip person.productIterator).foldLeft(dataFrame) {
  case (dataFrame, (name, value)) =>
    dataFrame.withColumn(name, value) // example
}

这会带来运行时损失,但不需要任何依赖关系。

另一个选择是使用无形状或木兰来计算编译时的结果(前提是您知道要从中提取字段名称的类型)。

无形状解决方案是already provided in another question

木兰解决方案类似于(免责声明:如果编译,则未经测试):

import magnolia._

trait FieldNames[T] {
  def apply(): List[String]
}

object FieldNames {
  def getNames[T](implicit fieldNames: FieldNames[T]): FieldNames[T] = fieldNames

  type Typeclass[T] = FieldNames[T]

  def combine[T](ctx: ReadOnlyCaseClass[Typeclass, T]): FieldNames[T] = () =>
    ctx.parameters.map(_.label).toList
  }

  implicit def gen[T]: FieldNames[T] = macro Magnolia.gen[T]
}

(FieldNames.getNames[Person] zip person.productIterator).foldLeft(dataFrame) {
  case (dataFrame, (name, value)) =>
    dataFrame.withColumn(name, value) // example
}

编译时反映需要更多的努力,并假定您知道在编译时要使用的值的类型,但是在运行时应该更快,并且更不容易出错。

长话短说,哪个更好取决于您的用例。

© www.soinside.com 2019 - 2024. All rights reserved.