使用 Spark 加载时,混合列中的空字符串会使一行无效

Empty string in mixed column nullifies a row when loaded using Spark

考虑以下 JSON:

{"col1": "yoyo", "col2": 1.5}
{"col1": "",     "col2": 6}
{"col1": "456",  "col2": ""}
{"col1": 444,    "col2": 12}
{"col1": null,   "col2": 1.7}
{"col1": 3.14,   "col2": null}

我使用 (Py)Spark 加载如下:

from pyspark.sql import SparkSession
spark = SparkSession.builder.master("local[*]").getOrCreate()
df = spark.read.json("my.json")
df.show()

产生:

+----+----+
|col1|col2|
+----+----+
|yoyo| 1.5|
|    | 6.0|
|null|null|  <---===***
| 444|12.0|
|null| 1.7|
|3.14|null|
+----+----+

我很难理解为什么第三行无效。 似乎原因是第二列中唯一的字符串是空字符串 "" 并且这以某种方式导致无效。 请注意 col1 在第二行也包含一个空字符串,但该行 无效。

对我来说,这是一个非常令人困惑和意外的行为。 我无法在文档中找到提示。

使用 Spark 时无法在单个列中混合不同的数据类型

读取json文件时,Spark会尝试推断每一列的数据类型(详情见底部注释)。这里,Spark认为col1是string类型,col2是double类型。这可以通过阅读 json 文件并在数据帧上使用 printSchema 来确认。
这意味着数据是根据这些推断的数据类型进行解析的。因此,Spark 将尝试将 "" 解析为显然失败的双精度值。 (对于 col1 中的第二行,它有效,因为 col1 被推断为字符串类型,因此 "" 是一个有效输入。)

使用 spark.read.json 时可以设置不同的模式。从 documentation 我们有:

mode -
allows a mode for dealing with corrupt records during parsing. If None is set, it uses the default value, PERMISSIVE.

  • PERMISSIVE: when it meets a corrupted record, puts the malformed string into a field configured by columnNameOfCorruptRecord, and sets other fields to null. To keep corrupt records, an user can set a string type field named columnNameOfCorruptRecord in an user-defined schema. If a schema does not have the field, it drops corrupt records during parsing. When inferring a schema, it implicitly adds a columnNameOfCorruptRecord field in an output schema.
  • DROPMALFORMED: ignores the whole corrupted records.
  • FAILFAST: throws an exception when it meets corrupted records.

从上面我们可以看出默认使用PERMISSIVE模式,如果遇到损坏的记录,所有字段都设置为null。这就是这种情况下发生的情况。为了确认,可以将 mode 设置为 FAILFAST

spark.read.json("my.json", mode='FAILFAST')

这会给出一个例外。

这可以通过不推断数据类型并将所有内容读取为字符串来解决。

spark.read.json("my.json", primitivesAsString='true')

注意: json 的模式推断与 csv 和 txt 等其他来源相比有点不同,请参阅 here。对于 json 文件,""null 都有特殊处理来处理不区分两者的 json 生成器。对于 csv 文件,具有空字符串 "" 的列仍然会使整个列被推断为字符串,但对于 json.

则不是这种情况

作为旁注,将 "" 替换为例如"5" in col2 将使推断的列类型为字符串。