我无法让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
似乎没有任何改变。
我错过了什么吗?
答案 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)