Spark默认空列DataSet

时间:2017-07-03 12:58:02

标签: json scala apache-spark dataset

我无法让Spark读取json(或csv)作为Dataset的{​​{1}}个案例类,其中Option[_]字段并非所有字段都在源中定义。

这有点神秘,但我要说我有一个名为CustomData的案例类

给出以下json文件(customA.json):

{"id":123, "colA": "x", "colB": "z"}
{"id":456, "colA": "y"}
{"id":789,              "colB": "a"}

以下代码:

import org.apache.spark.sql.SparkSession

val spark = SparkSession.builder()
  .master("local[2]")
  .appName("test")
  .getOrCreate()

import spark.implicits._

case class CustomData(id: BigInt, colA: Option[String], colB: Option[String])
org.apache.spark.sql.catalyst.encoders.OuterScopes.addOuterScope(this)

val ds = spark
  .read
  .option("mode", "PERMISSIVE")
  .json("src/main/resources/customA.json")
  .as[CustomData]
  .show()

输出是 - 正如预期的那样 - :

+----+----+---+
|colA|colB| id|
+----+----+---+
|   x|   z|123|
|   y|null|456|
|null|   a|789|
+----+----+---+

尽管并非总是定义所有列。 但是,如果我想使用相同的代码来读取其中一列无处出现的文件,我就无法实现:

对于其他json文件(customB.json):

{"id":321, "colA": "x"}
{"id":654, "colA": "y"}
{"id":987}

附加代码:

  val ds2 = spark
  .read
  .option("mode", "PERMISSIVE")
  .json("src/main/resources/customB.json")
  .as[CustomData]
  .show()

输出错误:

org.apache.spark.sql.AnalysisException:无法解析给定输入列的“colB”:[colA,id];

这是有道理的,但我很乐意为两个文件重用相同的案例类。特别是如果我不知道在摄取它之前甚至在json文件中出现了colB

当然我可以进行检查,但有没有办法将不存在的列转换为null(与customA.json一样)。将readmode设置为Permissive似乎没有任何改变。

我错过了什么吗?

2 个答案:

答案 0 :(得分:1)

我将一个答案放在这里。向你展示什么(有点)有效,但看起来非常 hacky恕我直言。

通过一种方法扩展DataFrame以强制案例类的StructType在已存在的StructType之上它实际上有效,但也许(我真的希望)有更好/更清洁的解决方案

这里是:

import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.functions._
import org.apache.spark.sql.catalyst.ScalaReflection
import scala.reflect.runtime.universe._

case class DataFrameExtended(dataFrame: DataFrame) {

  def forceMergeSchema[T: TypeTag]: DataFrame = {
    ScalaReflection
      .schemaFor[T]
      .dataType
      .asInstanceOf[StructType]
      .filterNot(
        field => dataFrame.columns.contains(field.name)
      )
      .foldLeft(dataFrame){
        case (newDf, field) => newDf.withColumn(field.name, lit(null).cast(field.dataType))
      }
  }
}

implicit def dataFrameExtended(df: DataFrame): DataFrameExtended = {
  DataFrameExtended(df)
}

val ds2 = spark
  .read
  .option("mode", "PERMISSIVE")
  .json("src/main/resources/customB.json")
  .forceMergeSchema[CustomData]
  .as[CustomData]
  .show()

现在显示我希望的结果:

+----+---+----+
|colA| id|colB|
+----+---+----+
|   x|321|null|
|   y|654|null|
|null|987|null|
+----+---+----+

我只尝试使用标量类型(如Int,String等),我认为更复杂的结构会失败。所以我仍然在寻找更好的答案。

答案 1 :(得分:0)

这是一个更简单的解决方案:

    import org.apache.spark.sql.types.StructType
    import org.apache.spark.sql.DataFrame
    import org.apache.spark.sql.functions._
    import org.apache.spark.sql.catalyst.ScalaReflection
    import scala.reflect.runtime.universe._

val structSchema = ScalaReflection.schemaFor[CustomData].dataType.asInstanceOf[StructType]
val df = spark.read.schema(structSchema).json(jsonRDD)