如何用混合嵌套和非嵌套结构解析json?

时间:2017-10-29 11:58:08

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

在文件1中,/URI元素“image”是嵌套的。数据看起来像这样结构:

/URI

Spark:

正确推断出结果模式
JSON

文件nested2.json包含一些嵌套元素和一些非嵌套元素(在第二行下面,元素“image”未嵌套):

{"id": "0001", "type": "donut", "name": "Cake", "image":{"url": "images/0001.jpg", "width": 200, "height": 200}}

结果架构不包含嵌套数据:

val df1 = spark.read.json("/xxx/xxxx/xxxx/nested1.json")
df1.printSchema

root
 |-- id: string (nullable = true)
 |-- image: struct (nullable = true)
 |    |-- height: long (nullable = true)
 |    |-- url: string (nullable = true)
 |    |-- width: long (nullable = true)
 |-- name: string (nullable = true)
 |-- type: string (nullable = true)

为什么Spark存在非嵌套元素时无法找出架构?

如何使用Spark和Scala处理包含这类记录混合的{"id": "0001", "type": "donut", "name": "Cake", "image":{"url": "images/0001.jpg", "width": 200, "height": 200}} {"id": "0002", "type": "donut", "name": "CupCake", "image": "images/0001.jpg"} 文件?

1 个答案:

答案 0 :(得分:1)

通过不指定模式,Spark将通过读取数据一次从数据源推断出它。查看source code推断json数据,代码中有一条评论:

  

分三个阶段推断json记录集合的类型:

     
      
    1.   
    2. 推断每条记录的类型
    3.   
  •   
    1.   
    2. 通过选择覆盖相等键所需的最低类型来合并类型
    3.   
  •   
    1.   
    2. 将所有剩余的空字段替换为字符串,顶部类型
    3.   
  •   

换句话说,当存在差异时,仅返回最常用的数据类型。通过混合嵌套和非嵌套类型,只返回非嵌套类型,因为所有行都包含这些值。

不是推断架构,而是自己创建架构并将其提供给方法。

val schema = StructType( 
  StructField("id", StringType, true) ::
  StructField("type", StringType, true) ::
  StructField("name", StringType, true) ::
  StructField(
    "image",
    StructType( 
      StructField("height", LongType, true) ::
      StructField("url", StringType, true) ::
      StructField("width", LongType, true) ::
      Nil
    ))
  ) :: Nil
)

然后在阅读时使用架构:

val df2 = spark.read
  .schema(schema)
  .json("/xxx/xxx/xxx/nested2.json")

但是,当它只是一个字符串时,这种方法将导致“image”字段的 null 。要获取这两种类型,请两次读取文件并合并结果。

val df3 = spark.read
  .json("/xxx/xxx/xxx/nested2.json")

新数据框架与第一个架构有不同的架构。让它们相等,然后将数据帧合并在一起:

val df4 = df3.withColumn("image", 
    struct($"image".as("url"), 
           lit(0L).as("height"), 
           lit(0L).as("width"))
  .select("id", "type", "name", "image")

val df5 = df2.union(df4)

最后select用于确保列与df2的顺序相同,否则union将失败。