任何可以消除我的 Spark 模式消除器中的 asInstanceOf 和 Any 的 scala 技巧?
any scala trick that would eliminate asInstanceOf and Any in my Spark schema de-nullifier?
这并不是一个真正的 Spark 问题,而是一个与 Scala 类型相关的问题,但我正在做的事情可能会引起 Spark 粉丝的兴趣,所以我将 'Spark' 保留在我的框架中的问题,即:
I want to recursively transform a spark sql schema of StructType,
which contains a list whose elements may be either StructType's or
StructField's. The result of the transform should be a version of the original schema which disallows nulls in any field. Unfortunately, StructType and StructField don't extend
from a common marker trait. This lead to my initial implementation
where the method accepted 'Any' and explicitly cast the result back to
StructType.
初步实施
object SchemaTools extends App {
import org.apache.spark.sql.types._
def noNullSchema(schema: StructType): StructType = {
def go(element: Any): Product = element match {
case x: StructField => x.copy(nullable = false)
case x: StructType => StructType(x.fields.map(_.copy(nullable = false)))
case bad => sys.error(s"element of unexpected type: $bad")
}
go(schema).asInstanceOf[StructType]
}
type Rec = (String, Seq[(Int, Int, String)])
val schema: StructType = Encoders.product[Rec].schema
System.out.println("pr:" + schema.prettyJson)
System.out.println("pr:" + noNullSchema(schema).prettyJson)
}
更新
我接受 Tim 的回答,因为他友好地指出了我的愚蠢错误,即我没有递归到嵌套结构中。我在下面包含了上述 "proof of concept" 的修改版本。这适用于我的示例输入,并说明了我将采用的一般方法。通过此实现,我没有与类型相关的问题。我的错! :我误解了 StructType 中的内容(它始终是 StructField 的数组,而不是 StructField 或 StructType 的数组)。数组中的字段本身可能是 "StructType" 数据类型,这推动了递归的需要。无论如何......下面是一个修改后的 "toy" 实现,它说明了如果我需要一个完整的解决方案(而不是仅仅为了学习而实现),我将如何解决这个问题。此代码绝对不是生产就绪的,并且会在更复杂的输入上失败。不过,它说明了一种可能的方法。
注意:关于 null 和模式,我了解到的另一件事是非常重要的,请牢记......即使正确实现了模式 "de-nuller" Spark 也不会在解析期间强制执行可空性检查。此处对此进行了更详细的讨论:
*概念证明......不再有类型问题*
object SchemaTools extends App {
import org.apache.spark.sql.types._
def noNullSchema(field: StructField): StructField = {
field.dataType match {
case ArrayType(StructType(fields), containsNull) =>
StructField(
field.name,
ArrayType(noNullSchema(StructType(fields)), containsNull),
nullable = false,
field.metadata)
case _ => field.copy(nullable = false)
}
}
def noNullSchema(schema: StructType): StructType =
StructType (
schema.fields.map { f =>
System.out.println("f:" + f);
noNullSchema(f)
}
)
type Rec = (String, Seq[(Int, String, String)])
val schema: StructType = Encoders.product[Rec].schema
System.out.println("pr:" + schema.prettyJson)
System.out.println("pr:" + noNullSchema(schema).prettyJson)
}
除非我误解了这个问题,否则我认为你只需要调整你原来的解决方案就可以改变
go(schema).asInstanceOf[StructType]
进入
StructType(schema.fields.map(go))
此外,go
的参数和结果类型应与 StructType.fields
的元素类型相同。
由于框架对 fields
的元素使用通用类型,因此必须有一些代码来处理该通用类型。因此,如果该类型是 Any
,那么您必须处理 Any
,而类型类将无济于事。
这并不是一个真正的 Spark 问题,而是一个与 Scala 类型相关的问题,但我正在做的事情可能会引起 Spark 粉丝的兴趣,所以我将 'Spark' 保留在我的框架中的问题,即:
I want to recursively transform a spark sql schema of StructType, which contains a list whose elements may be either StructType's or StructField's. The result of the transform should be a version of the original schema which disallows nulls in any field. Unfortunately, StructType and StructField don't extend from a common marker trait. This lead to my initial implementation where the method accepted 'Any' and explicitly cast the result back to StructType.
初步实施
object SchemaTools extends App {
import org.apache.spark.sql.types._
def noNullSchema(schema: StructType): StructType = {
def go(element: Any): Product = element match {
case x: StructField => x.copy(nullable = false)
case x: StructType => StructType(x.fields.map(_.copy(nullable = false)))
case bad => sys.error(s"element of unexpected type: $bad")
}
go(schema).asInstanceOf[StructType]
}
type Rec = (String, Seq[(Int, Int, String)])
val schema: StructType = Encoders.product[Rec].schema
System.out.println("pr:" + schema.prettyJson)
System.out.println("pr:" + noNullSchema(schema).prettyJson)
}
更新
我接受 Tim 的回答,因为他友好地指出了我的愚蠢错误,即我没有递归到嵌套结构中。我在下面包含了上述 "proof of concept" 的修改版本。这适用于我的示例输入,并说明了我将采用的一般方法。通过此实现,我没有与类型相关的问题。我的错! :我误解了 StructType 中的内容(它始终是 StructField 的数组,而不是 StructField 或 StructType 的数组)。数组中的字段本身可能是 "StructType" 数据类型,这推动了递归的需要。无论如何......下面是一个修改后的 "toy" 实现,它说明了如果我需要一个完整的解决方案(而不是仅仅为了学习而实现),我将如何解决这个问题。此代码绝对不是生产就绪的,并且会在更复杂的输入上失败。不过,它说明了一种可能的方法。
注意:关于 null 和模式,我了解到的另一件事是非常重要的,请牢记......即使正确实现了模式 "de-nuller" Spark 也不会在解析期间强制执行可空性检查。此处对此进行了更详细的讨论:
*概念证明......不再有类型问题*
object SchemaTools extends App {
import org.apache.spark.sql.types._
def noNullSchema(field: StructField): StructField = {
field.dataType match {
case ArrayType(StructType(fields), containsNull) =>
StructField(
field.name,
ArrayType(noNullSchema(StructType(fields)), containsNull),
nullable = false,
field.metadata)
case _ => field.copy(nullable = false)
}
}
def noNullSchema(schema: StructType): StructType =
StructType (
schema.fields.map { f =>
System.out.println("f:" + f);
noNullSchema(f)
}
)
type Rec = (String, Seq[(Int, String, String)])
val schema: StructType = Encoders.product[Rec].schema
System.out.println("pr:" + schema.prettyJson)
System.out.println("pr:" + noNullSchema(schema).prettyJson)
}
除非我误解了这个问题,否则我认为你只需要调整你原来的解决方案就可以改变
go(schema).asInstanceOf[StructType]
进入
StructType(schema.fields.map(go))
此外,go
的参数和结果类型应与 StructType.fields
的元素类型相同。
由于框架对 fields
的元素使用通用类型,因此必须有一些代码来处理该通用类型。因此,如果该类型是 Any
,那么您必须处理 Any
,而类型类将无济于事。