【问题标题】:Spark: JSON Nullable fields handlingSpark:JSON 可空字段处理
【发布时间】:2019-12-14 23:33:24
【问题描述】:

我已经对此提出了一个问题,但那是针对Array type column。 最初认为该问题仅发生在 JSON 数组字段中,但看起来发生了这种情况 甚至是一个简单的标量字段。

下面是我正在加载的数据。

val ds = spark.read.textFile("./src/main/resources/json/jsonwithnullfield.txt").as[String]
ds.show(false)

+--------------------------------------------------------------------+
|value                                                               |
+--------------------------------------------------------------------+
|{"title": {"titleId": "111", "titleName": "AAA", "titleDesc": null}}|
|{"title": {"titleId": "222", "titleName": "BBB", "titleDesc": null}}|
|{"title": {"titleId": "333", "titleName": "CCC", "titleDesc": null}}|
|{"title": {"titleId": "444", "titleName": "DDD", "titleDesc": null}}|
|{"title": {"titleId": "555", "titleName": "EEE", "titleDesc": null}}|
+--------------------------------------------------------------------+

然后将 Dataset[String] 加载为 JSON 我看到了schema 中的所有列,包括titleDesc 字段。

val jsonDF = spark.read.json(ds)
jsonDF.printSchema()
jsonDF.show(false)

root
 |-- title: struct (nullable = true)
 |    |-- titleDesc: string (nullable = true)
 |    |-- titleId: string (nullable = true)
 |    |-- titleName: string (nullable = true)

+------------+
|title       |
+------------+
|[, 111, AAA]|
|[, 222, BBB]|
|[, 333, CCC]|
|[, 444, DDD]|
|[, 555, EEE]|
+------------+

然后我使用 to_json functiontitle 值转换为 JSON,但结果似乎没有按预期工作 因为我在 JSON 值中没有看到 titleDesc 字段。

jsonDF.select(to_json(struct($"title.*")).as("Title")).show(false)

输出:

+-----------------------------------+
|Title                              |
+-----------------------------------+
|{"titleId":"111","titleName":"AAA"}|
|{"titleId":"222","titleName":"BBB"}|
|{"titleId":"333","titleName":"CCC"}|
|{"titleId":"444","titleName":"DDD"}|
|{"titleId":"555","titleName":"EEE"}|
+-----------------------------------+

我想查看 JSON 输出字符串中的 titleDesc 字段。 如果无法通过to_json 函数处理,是否有解决方法?

【问题讨论】:

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


    【解决方案1】:

    Edit 1:如 cmets 中所述,in 可能仅适用于 Spark 3

    根据您的 Spark 版本,您可以在应用 to_json 内置函数时尝试使用 ignoreNullFields 选项。 会给出类似的东西:

    df.select(to_json($"title", Map("ignoreNullFields" -> "false")).as("myfulljson"))
    

    见这里,第 81 行:https://github.com/apache/spark/blob/master/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/json/JSONOptions.scala

    编辑 2:按照建议,您可以使用 coalesce 函数将空值替换为空字符串:

    import spark.implicits._
    
    val jsonDF = spark.read.json(Seq(
      """{"title": {"titleId": "111", "titleName": "AAA", "titleDesc": null}}""",
      """{"title": {"titleId": "222", "titleName": "BBB", "titleDesc": null}}""",
      """{"title": {"titleId": "333", "titleName": "CCC", "titleDesc": null}}""",
      """{"title": {"titleId": "444", "titleName": "DDD", "titleDesc": null}}""",
      """{"title": {"titleId": "555", "titleName": "EEE", "titleDesc": null}}"""
    ).toDS).select("title.*")
    
    jsonDF.show(false)
    
    +---------+-------+---------+
    |titleDesc|titleId|titleName|
    +---------+-------+---------+
    |null     |111    |AAA      |
    |null     |222    |BBB      |
    |null     |333    |CCC      |
    |null     |444    |DDD      |
    |null     |555    |EEE      |
    +---------+-------+---------+
    
    jsonDF.select(to_json(struct(jsonDF.columns.map{c => coalesce(col(c), lit("")).as(c)}:_*)).as("title"))
      .show(false)
    
    +--------------------------------------------------+
    |title                                             |
    +--------------------------------------------------+
    |{"titleDesc":"","titleId":"111","titleName":"AAA"}|
    |{"titleDesc":"","titleId":"222","titleName":"BBB"}|
    |{"titleDesc":"","titleId":"333","titleName":"CCC"}|
    |{"titleDesc":"","titleId":"444","titleName":"DDD"}|
    |{"titleDesc":"","titleId":"555","titleName":"EEE"}|
    +--------------------------------------------------+
    

    【讨论】:

    • 谢谢@@baitmbarek。我使用spark 2.4.3 似乎不起作用。有什么想法吗?
    • 这个选项怎么样:“dropFieldIfAllNull”?
    • 试过了。还是没有运气!
    • ignoreNullFields 是 Spark 3.x 的一个新功能 :( 我猜你必须使用你自己的带有 udf 的 Jackson Mapper
    【解决方案2】:

    这个怎么样?

    spark.read.json(spark.read.textFile("file.txt")).withColumn("title",struct(col("title.titleId"),col("title.titleName"),lit("").as("titleDesc"))).select(to_json('title)).show(false)
    

    【讨论】:

      【解决方案3】:

      这是我目前找到的解决方法。

      ds.withColumn("value",regexp_replace($"value","null", s"""""""")).as[String]
      

      输出:

      jsonDF.show(false)
      
      +--------------------------------------------------+
      |Title                                             |
      +--------------------------------------------------+
      |{"titleDesc":"","titleId":"111","titleName":"AAA"}|
      |{"titleDesc":"","titleId":"222","titleName":"BBB"}|
      |{"titleDesc":"","titleId":"333","titleName":"CCC"}|
      |{"titleDesc":"","titleId":"444","titleName":"DDD"}|
      |{"titleDesc":"","titleId":"555","titleName":"EEE"}|
      +--------------------------------------------------+
      

      【讨论】:

      • 很高兴您找到了适合您需要的解决方法。对于相同的输出,您可以使用 coalesce 内置函数为您处理 null 值,并将它们替换为您想要的任何值。它比正则表达式更安全、更高效;)
      • 你能举个例子吗?
      • 当然,我会用这个例子在几分钟内编辑我自己的回复
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-10
      • 1970-01-01
      • 2018-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多