在Spark结构流中读取嵌套Json

时间:2019-06-11 14:51:27

标签: json apache-spark spark-structured-streaming

我正在尝试使用结构化流从Kafka读取数据。从kafka接收的数据为json格式。我使用示例json创建架构,然后在代码中稍后使用from_json函数将json转换为数据框以进行进一步处理。我面临的问题是嵌套模式和多值。该示例架构将标记(例如a)定义为结构。从kafka读取的json数据可以为同一标签具有一个或多个值(两个不同的值)。

val df0= spark.read.format("json").load("contactSchema0.json")
val schema0 = df0.schema
val df1 = spark.readStream.format("kafka").option("kafka.bootstrap.servers", "node1:9092").option("subscribe", "my_first_topic").load()
val df2 = df1.selectExpr("CAST(value as STRING)").toDF()
val df3 = df2.select(from_json($"value",schema0).alias("value")) 

contactSchema0.json具有一个示例标记,如下所示:

"contactList": {
        "contact": [{
          "id": 1001
},
{
 "id": 1002
}]
}

因此,联系被推断为结构。但是从Kafka读取的JSON数据也可以具有如下数据:

"contactList": {
                "contact": {
                  "id": 1001
        }
    }

因此,如果我将架构定义为结构,则spark.json无法推断单个值,并且如果我将架构定义为字符串spark.json无法推断多个值。

1 个答案:

答案 0 :(得分:1)

Spark JSON Options中找不到此类功能,但杰克逊具有this answer中所述的DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY

所以我们可以解决这样的问题

case class MyModel(contactList: ContactList)
case class ContactList(contact: Array[Contact])
case class Contact(id: Int)

val txt =
  """|{"contactList": {"contact": [{"id": 1001}]}}
     |{"contactList": {"contact": {"id": 1002}}}"""
    .stripMargin.lines.toSeq.toDS()

txt
  .mapPartitions[MyModel] { it: Iterator[String] =>
    val reader = new ObjectMapper()
      .registerModule(DefaultScalaModule)
      .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
      .readerFor(classOf[MyModel])
    it.map(reader.readValue[MyModel])
  }
  .show()

输出:

+-----------+
|contactList|
+-----------+
| [[[1001]]]|
| [[[1002]]]|
+-----------+

请注意,要在代码中获得Dataset,可以使用

val df2 = df1.selectExpr("CAST(value as STRING)").as[String]

相反,然后像以前一样为mapPartitions呼叫df2