我正在使用scala在spark中处理不同类型和不同模式的流事件,我需要解析它们,并将它们保存为易于以常规方式进一步处理的格式。
我有一个如下所示的事件数据框:
val df = Seq(("{\"a\": 1, \"b\": 2, \"c\": 3 }", "One", "001") ,("{\"a\": 6, \"b\": 2, \"d\": 2, \"f\": 8 }", "Two", "089"), ("{\"a\": 3, \"b\": 4, \"c\": 6 }", "One", "123")).toDF("col1", "col2", "col3")
这是
+------------------------------------+--------+------+
| body | type | id |
+------------------------------------+--------+------+
|{"a": 1, "b": 2, "c": 3 } | "One"| 001|
|{"a": 6, "d": 2, "f": 8, "g": 10} | "Two"| 089|
|{"a": 3, "b": 4, "c": 6 } | "Three"| 123|
+------------------------------------+--------+------+
,我想把它变成这个。我们可以假设所有类型“ One”将具有相同的架构,并且所有类型的事件都将共享一些相似的数据,例如条目“ a”,我希望将其显示在其自己的列中
+---+--------------------------------+--------+------+
| a | data | y | z |
+---+--------------------------------+--------+------+
| 1 |{"b": 2, "c": 3 } | "One"| 001|
| 6 |{"d": 2, "f": 8, "g": 10} | "Two"| 089|
| 3 |{"b": 4, "c": 6 } | "Three"| 123|
+------------------------------------+--------+------+
答案 0 :(得分:2)
一种实现方法是将json数据作为Map处理,如下所示:
import org.apache.spark.sql.types.{MapType, StringType, IntegerType}
import org.apache.spark.sql.functions.{from_json, expr}
val df = Seq(
("{\"a\": 1, \"b\": 2, \"c\": 3 }", "One", "001") ,
("{\"a\": 6, \"b\": 2, \"d\": 2, \"f\": 8 }", "Two", "089"),
("{\"a\": 3, \"b\": 4, \"c\": 6 }", "One", "123")
).toDF("body", "type", "id")
val mapSchema = MapType(StringType, IntegerType)
df.withColumn("map", from_json($"body", mapSchema))
.withColumn("data_keys", expr("filter(map_keys(map), k -> k != 'a')"))
.withColumn("data_values", expr("transform(data_keys, k -> element_at(map,k))"))
.withColumn("data", expr("to_json(map_from_arrays(data_keys, data_values))"))
.withColumn("a", $"map".getItem("a"))
.select($"a", $"data", $"type".as("y"), $"id".as("z"))
.show(false)
// +---+-------------------+---+---+
// |a |data |y |z |
// +---+-------------------+---+---+
// |1 |{"b":2,"c":3} |One|001|
// |6 |{"b":2,"d":2,"f":8}|Two|089|
// |3 |{"b":4,"c":6} |One|123|
// +---+-------------------+---+---+
withColumn("map", from_json($"body", mapSchema))
:首先根据给定的json数据生成一个Map。withColumn("data_keys", expr("filter(map_keys(map), k -> k != 'a')"))
:通过滤除不等于a
的键来检索新地图的键。我们在此处使用filter函数,该函数返回一个数组,即{"a": 1, "b": 2, "c": 3 } -> [b, c]
。withColumn("data_values", expr("transform(data_keys, k -> element_at(map,k))"))
:结合使用先前的键和transform来填充新地图的值。withColumn("data", expr("to_json(map_from_arrays(data_keys, data_values))"))
:使用map_from_arrays从data_keys
和data_values
生成地图。最后,调用to_json
将地图转换回json。答案 1 :(得分:0)
首先,您需要按以下方式定义json模式:
val schema = spark.read.json(df.select("col1").as[String]).schema
然后,您可以将列col1
转换为json(第一行),然后只需选择要提取的json元素(第二行)即可:
df.select(from_json($"col1", schema).as("data"), $"col2", $"col3")
.select($"data.a", $"data", $"col2", $"col3")
输出:
+---+-------------+----+----+
| a| data|col2|col3|
+---+-------------+----+----+
| 1| [1, 2, 3,,]| One| 001|
| 6|[6, 2,, 2, 8]| Two| 089|
| 3| [3, 4, 6,,]| One| 123|
+---+-------------+----+----+
我知道它与您想要的不完全相同,但是它将为您提供线索。
其他选项,如果您想完全解构json,则可以使用数据。*
df.select(from_json($"col1", schema).as("data"), $"col2", $"col3").select($"data.*", $"col2", $"col3")
+---+---+----+----+----+----+----+
| a| b| c| d| f|col2|col3|
+---+---+----+----+----+----+----+
| 1| 2| 3|null|null| One| 001|
| 6| 2|null| 2| 8| Two| 089|
| 3| 4| 6|null|null| One| 123|
+---+---+----+----+----+----+----+