Spark DataFrame序列化为无效的json

时间:2018-01-29 14:21:49

标签: json apache-spark apache-spark-sql spark-dataframe

TL; DR :当我将一个Spark DataFrame转储为json时,我总是得到像

这样的东西
{"key1": "v11", "key2": "v21"}
{"key1": "v12", "key2": "v22"}
{"key1": "v13", "key2": "v23"}

这是无效的json。我可以手动编辑转储文件以获得我可以解析的内容:

[
  {"key1": "v11", "key2": "v21"},
  {"key1": "v12", "key2": "v22"},
  {"key1": "v13", "key2": "v23"}
]

但我很确定我错过了一些可以让我避免这种手动编辑的东西。我现在不知道。

更多详情

我有一个org.apache.spark.sql.DataFrame,我尝试使用以下代码将其转储到json:

myDataFrame.write.json("file.json")

我也尝试过:

myDataFrame.toJSON.saveAsTextFile("file.json")

在这两种情况下,它最终都会正确地转储每一行,但它缺少行之间的分隔逗号,以及方括号。 因此,当我随后尝试解析此文件时,我使用的解析器会侮辱我然后失败。

我将很高兴知道如何转储有效的json。 (阅读DataFrameWriter的文档没有给我提供任何有趣的提示。)

1 个答案:

答案 0 :(得分:1)

这是预期的输出。 Spark使用JSON Lines之类的格式有很多原因:

  • 它可以并行解析和加载。
  • 解析可以在不加载内存中的完整文件的情况下完成。
  • 可以并行书写。
  • 可以在不将完整分区存储在内存中的情况下编写。
  • 即使文件为空,也是有效输入。
  • 最后,Spark中的Row是一个映射到JSON对象而不是数组的结构。
  • ...

您可以通过几种方式创建所需的输出,但它始终与上述之一冲突。

例如,您可以为每个分区编写一个JSON 文档

import org.apache.spark.sql.functions._

df
  .groupBy(spark_partition_id)
  .agg(collect_list(struct(df.columns map col: _*)).alias("data"))
  .select($"data")
  .write
  .json(output_path)

您可以在repartition(1)前面添加一个输出文件,但这不是您想要做的事情,除非数据非常小。

1.6替代方案将是glom

import org.apache.spark.sql.Row
import org.apache.spark.sql.types._

val newSchema = StructType(Seq(StructField("data", ArrayType(df.schema))))

sqlContext.createDataFrame(
  df.rdd.glom.flatMap(a => if(a.isEmpty) Seq() else Seq(Row(a))), 
  newSchema
)