:)
我想在案例类中引用变量名称(只是名称,没有值)。
接下来是一个非常简化的示例:
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 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
}
编译时反映需要更多的努力,并假定您知道在编译时要使用的值的类型,但是在运行时应该更快,并且更不容易出错。
长话短说,哪个更好取决于您的用例。