リモート開発メインのソフトウェア開発企業のエンジニアブログです

Spark 2.x で null を含む JSON を書き出す方法

Spark 2.x では、 DataFrame を JSON に書き出す際、値が null のデータは失われます。

次のコードを見てみましょう:

case class Person(name: String, age: Option[Int])

val people: Dataset[Person] = Seq(
  Person("Sabrina Carpenter", Some(22)),
  Person("Olivia Rodrigo", None)
).toDF.as[Person]

定義した Person の age はオプショナルにしており、作成した DataFrame の 2 番目のインスタンスでは None を渡しています。この DF を出力してみます:

(Spark console)

scala> people.show
+-----------------+----+
|             name| age|
+-----------------+----+
|Sabrina Carpenter|  22|
|   Olivia Rodrigo|null|
+-----------------+----+

では、 JSON ファイルに書き出してみます:

people.repartition(1).write.json("/tmp/people")

出力は次のようになります:

# /tmp/people/part-00000-780df354-920a-41e5-b799-5b954941ae1b-c000.json
{"name":"Sabrina Carpenter","age":22}
{"name":"Olivia Rodrigo"}

この通り、2 番目の要素 (“Olivia Rodrigo”) では age が null である為失われてしまいました。このデータを再度 Spark で読み込む分には問題がありませんが、今回これを使うシステムでは nullable なフィールドであっても指定が必須であった為、この問題に対処する必要がありました。

※ Spark 3.x 以降では spark.sql.jsonGenerator.ignoreNullFields と言うオプションができ、これに false を指定するだけで NULL を書き出す事ができます。具体的には spark.write.option("ignoreNullFields", "false").json(...) の様にして使います。

Moba Pro

対処方法

もの凄い力技ですが、 DataFrame の各行について、 Person オブジェクトの JSON 表現 (String 型) へのシリアライズを自前で行い、 JSON ではなく Text Writer を使ってプレーンテキストで書き込むと言う手法を取りました。

コードにするとこんな感じになります:

def serializePersonToJson(p: Person): String = 
  // お好きな JSON シリアライザをお使い下さい。この例では json4s を使います
  org.json4s.jackson.Serialization.write(p)(org.json4s.DefaultFormats.preservingEmptyValues)

val jsonSerializedPeople: Dataset[String] = people.map(serializePersonToJson(_))

では DF の中身を出力してみます:

(Spark console)

scala> jsonSerializedPeople.show
+-------------------------------------+
|value                                |
+-------------------------------------+
|{"name":"Sabrina Carpenter","age":22}|
|{"name":"Olivia Rodrigo","age":null} |
+-------------------------------------+

“Olivia Rodrigo” の age が残された形で JSON 化できました。最後にプレーンテキストとしてファイルに書き出してみます:

jsonSerializedPeople.repartition(1).write.mode("overwrite").text("/tmp/people")

中身を見てみます:

# /tmp/people/part-00000-8994f145-65b3-4f92-9e4c-cdc876b4aa37-c000.txt
{"name":"Sabrina Carpenter","age":22}
{"name":"Olivia Rodrigo","age":null}

こちらでも無事出力を確認できました。

※ちなみに今回使うシステムでは出力される JSON ファイルは単一である必要があるので、以前書いた SparkでDataFrameの内容を単一のファイルに保存する を併せて使っています。

参考にした Stack Overflow

https://stackoverflow.com/questions/44271612/retain-keys-with-null-values-while-writing-json-in-spark

← 前の投稿

Terraform で Amazon Lightsail 上に WordPress インスタンスを立てる

次の投稿 →

AnsibleのEC2インベントリ機能を使って自動でIPアドレスを割り当てる

コメントを残す