簡體   English   中英

使用 Spark 將帶有可選字段的嵌套 json 轉換為 Scala 案例類不起作用

[英]Using Spark converting nested json with optional fields to Scala case class not working

我有一個用例,我需要在 scala 中使用 spark 作為 Dataset[T] 讀取 json 文件或 json 字符串。 json 文件有嵌套元素,json 中的一些元素是可選的。 如果我忽略 json 中的可選字段,因為架構與案例類匹配,我可以讀取 json 文件並將它們映射到案例類。

根據此鏈接和答案,當案例類具有選項字段時,它適用於第一級 json,但如果存在嵌套元素,則它不起作用。

我正在使用的 Json 字符串如下:

val jsonString = """{
  "Input" :
    {
      "field1" : "Test1",
      "field2" : "Test2",
      "field3Array" : [
      {
      "key1" : "Key123",
      "key2" : ["keyxyz","keyAbc"]
      }
                ]
    },
  "Output": 
    {
      "field1" : "Test2",
      "field2" : "Test3",
      "requiredKey" : "RequiredKeyValue",
      "field3Array" : [
      {
      "key1" : "Key123",
      "key2" : ["keyxyz","keyAbc"]
      }
                ]
    }
}"""

我創建的案例類如下:

case class InternalFields (key1: String, key2 : Array[String])
case class Input(field1:String, field2: String,field3Array : Array[InternalFields])
case class Output(field1:String, field2: String,requiredKey : String,field3Array : Array[InternalFields])
case class ExternalObject(input : Input, output : Output)

我正在閱讀 jsonString 的代碼如下:

val df = spark.read.option("multiline","true").json(Seq(jsonString).toDS).as[ExternalObject]

上面的代碼工作得很好。 現在,當我在輸出案例類中添加一個可選字段作為 json 字符串可以讓它支持某些用例時,它會拋出一個錯誤,指出我在案例類中指定的可選字段丟失。

因此,為了解決這個問題,我繼續嘗試使用編碼器提供模式,看看是否可行。

添加可選字段后,我的案例類更改為如下:

case class InternalFields (key1: String, key2 : Array[String])
case class Input(field1:String, field2: String,field3Array : Array[InternalFields])
case class Output(field1:String, field2: String,requiredKey : String, optionalKey : Option[String],field3Array : Array[InternalFields])  //changed
case class ExternalObject(input : Input, output : Output)

在輸出案例類中添加了一個額外的可選字段。

現在我正在嘗試讀取 jsonString 如下:

import org.apache.spark.sql.Encoders
val schema = Encoders.product[ExternalObject].schema
val df = spark.read
           .schema(schema)
           .json(Seq(jsonString).toDS)
           .as[ExternalObject]

當我執行 df.show 或 display(df) 時,它會為我提供如下輸出表,輸入列和輸出列都為空。

在此處輸入圖像描述

如果我從案例類中刪除該可選字段,則此代碼也可以正常工作並向我顯示預期的輸出。

有什么方法可以使內部 json 或內部案例類中的這個可選字段工作並將其直接轉換為數據集 [T] 中的相應案例類。

任何可以使其發揮作用的想法、指導和建議都會有很大幫助。

問題是 spark 使用 struct 類型將類映射到Row ,以此為例:

case class MyRow(a: String, b: String, c: Option[String])

spark可以創建一個數據框,有時有c列,有時沒有? 喜歡:

+-----+-----+-----+
|  a  |  b  |  c  |
+-----+-----+-----+
| a1  | b1  |  c1 |
+-----+-----+-----+
| a2  | b2  |        <-- note the non-existence here :)
+-----+-----+-----+
| a3  | b3  | c3  |
+-----+-----+-----+

那么它不能,並且可以為空,意味着鍵必須存在,但值可以為空:

    ... other key values
    "optionalKey": null,
    ...

這被認為是有效的,並且可以轉換為您的結構。 我建議你使用一個專用的 JSON 庫(你知道那里有很多),並使用 udf 或其他東西從 json 中提取你需要的東西。

我用以下案例類結構測試了上面的代碼庫

case class Field3Array(
  key1: String,
  key2: List[String]
)
case class Input(
  field1: String,
  field2: String,
  field3Array: List[Field3Array]
)
case class Output(
  field1: String,
  field2: String,
  requiredKey: String,
  field3Array: List[Field3Array]
)
case class Root(
  Input: Input,
  Output: Output
)

Json 字符串不能像您嘗試過的那樣直接傳遞給 DataFrameReader,因為json方法需要一個路徑。 我將JSON字符串放在一個文件中,並將文件路徑傳遞給DataFrameReader,結果如下

import org.apache.spark.sql.{Encoder,Encoders}
import org.apache.spark.sql.Dataset

case class Field3Array(
  key1: String,
  key2: List[String]
)
case class Input(
  field1: String,
  field2: String,
  field3Array: List[Field3Array]
)
case class Output(
  field1: String,
  field2: String,
  requiredKey: String,
  field3Array: List[Field3Array]
)
case class Root(
  Input: Input,
  Output: Output
)


val pathToJson: String = "file:////path/to/json/file/on/local/filesystem"

val jsEncoder: Encoder[Root] = Encoders.product[Root]

val df: Dataset[Root] = spark.read.option("multiline","true").json(pathToJson).as[Root]

顯示結果如下:

df.show(false)

+--------------------------------------------+--------------------------------------------------------------+
|Input                                       |Output                                                        |
+--------------------------------------------+--------------------------------------------------------------+
|[Test1, Test2, [[Key123, [keyxyz, keyAbc]]]]|[Test2, Test3, [[Key123, [keyxyz, keyAbc]]], RequiredKeyValue]|
+--------------------------------------------+--------------------------------------------------------------+

df.select("Input.field1").show()

+------+
|field1|
+------+
| Test1|
+------+

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM