来自带有动态模式的_json的Spark
我试图使用Spark处理具有可变结构(嵌套JSON)的JSON数据。输入的JSON数据可能非常大,每行超过1000个键,一批可能超过20GB。 整个批处理由30个数据源生成,每个JSON的“key2”可用于标识源,并预定义了每个源的结构 处理此类数据的最佳方法是什么? 我尝试过使用from_json,如下所示,但它只适用于固定模式,要使用它,首先需要根据每个源对数据进行分组,然后应用模式。 由于数据量很大,我的首选方法是只扫描数据一次,然后根据预定义的模式从每个源中提取所需的值来自带有动态模式的_json的Spark,json,apache-spark,apache-spark-sql,Json,Apache Spark,Apache Spark Sql,我试图使用Spark处理具有可变结构(嵌套JSON)的JSON数据。输入的JSON数据可能非常大,每行超过1000个键,一批可能超过20GB。 整个批处理由30个数据源生成,每个JSON的“key2”可用于标识源,并预定义了每个源的结构 处理此类数据的最佳方法是什么? 我尝试过使用from_json,如下所示,但它只适用于固定模式,要使用它,首先需要根据每个源对数据进行分组,然后应用模式。 由于数据量很大,我的首选方法是只扫描数据一次,然后根据预定义的模式从每个源中提取所需的值 import o
import org.apache.spark.sql.types._
import spark.implicits._
val data = sc.parallelize(
"""{"key1":"val1","key2":"source1","key3":{"key3_k1":"key3_v1"}}"""
:: Nil)
val df = data.toDF
val schema = (new StructType)
.add("key1", StringType)
.add("key2", StringType)
.add("key3", (new StructType)
.add("key3_k1", StringType))
df.select(from_json($"value",schema).as("json_str"))
.select($"json_str.key3.key3_k1").collect
res17: Array[org.apache.spark.sql.Row] = Array([xxx])
如果你有问题中提到的数据
val data = sc.parallelize(
"""{"key1":"val1","key2":"source1","key3":{"key3_k1":"key3_v1"}}"""
:: Nil)
您不需要为json数据创建schema
。Spark sql可以从json字符串推断出schema
。您只需使用SQLContext.read.json
,如下所示
val df = sqlContext.read.json(data)
对于上面使用的rdd数据,它将为您提供如下所示的schema
您只需选择key3_k1
df2.select("key3.key3_k1").show(false)
//+-------+
//|key3_k1|
//+-------+
//|key3_v1|
//+-------+
您可以随心所欲地操作
数据帧
。我希望答案是有帮助的我不确定我的建议是否能帮助你,尽管我有一个类似的案例,我解决了它如下:
1) 因此,我们的想法是使用json rapture(或其他json库)来
动态加载JSON模式。例如,你可以阅读第1页
用于发现模式的json文件行(类似于我所做的
这里是jsonSchema)
2) 动态生成模式。首先遍历动态
字段(请注意,我将key3的值投影为Map[String,String])
并为模式中的每一个添加一个StructField
3) 将生成的模式应用到数据框架中
import rapture.json._
import jsonBackends.jackson._
val jsonSchema = """{"key1":"val1","key2":"source1","key3":{"key3_k1":"key3_v1", "key3_k2":"key3_v2", "key3_k3":"key3_v3"}}"""
val json = Json.parse(jsonSchema)
import scala.collection.mutable.ArrayBuffer
import org.apache.spark.sql.types.StructField
import org.apache.spark.sql.types.{StringType, StructType}
val schema = ArrayBuffer[StructField]()
//we could do this dynamic as well with json rapture
schema.appendAll(List(StructField("key1", StringType), StructField("key2", StringType)))
val items = ArrayBuffer[StructField]()
json.key3.as[Map[String, String]].foreach{
case(k, v) => {
items.append(StructField(k, StringType))
}
}
val complexColumn = new StructType(items.toArray)
schema.append(StructField("key3", complexColumn))
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
val sparkConf = new SparkConf().setAppName("dynamic-json-schema").setMaster("local")
val spark = SparkSession.builder().config(sparkConf).getOrCreate()
val jsonDF = spark.read.schema(StructType(schema.toList)).json("""your_path\data.json""")
jsonDF.select("key1", "key2", "key3.key3_k1", "key3.key3_k2", "key3.key3_k3").show()
我使用下一个数据作为输入:
{"key1":"val1","key2":"source1","key3":{"key3_k1":"key3_v11", "key3_k2":"key3_v21", "key3_k3":"key3_v31"}}
{"key1":"val2","key2":"source2","key3":{"key3_k1":"key3_v12", "key3_k2":"key3_v22", "key3_k3":"key3_v32"}}
{"key1":"val3","key2":"source3","key3":{"key3_k1":"key3_v13", "key3_k2":"key3_v23", "key3_k3":"key3_v33"}}
以及输出:
+----+-------+--------+--------+--------+
|key1| key2| key3_k1| key3_k2| key3_k3|
+----+-------+--------+--------+--------+
|val1|source1|key3_v11|key3_v21|key3_v31|
|val2|source2|key3_v12|key3_v22|key3_v32|
|val2|source3|key3_v13|key3_v23|key3_v33|
+----+-------+--------+--------+--------+
我还没有测试过的一个高级替代方案是,从JSON模式生成一个名为JsonRow的case类,以便拥有一个强类型数据集,该数据集提供了更好的序列化性能,同时使代码更易于维护。要做到这一点,首先需要创建一个JsonRow.scala文件,然后应该实现一个sbt预构建脚本,该脚本将根据源文件动态修改JsonRow.scala的内容(当然,您可能有多个)。要动态生成类JsonRow,可以使用以下代码:
def generateClass(members: Map[String, String], name: String) : Any = {
val classMembers = for (m <- members) yield {
s"${m._1}: String"
}
val classDef = s"""case class ${name}(${classMembers.mkString(",")});scala.reflect.classTag[${name}].runtimeClass"""
classDef
}
祝你好运这只是对@Ramesh Maharjan答案的重述,但使用了更现代的Spark语法 我在
DataFrameReader
中发现了这个方法,它允许您将Dataset[String]
中的JSON字符串解析为任意DataFrame
,并利用Spark在直接从JSON文件读取时使用Spark.read.JSON(“filepath”)
提供的相同模式推断。每行的模式可以完全不同
def json(jsonDataset: Dataset[String]): DataFrame
用法示例:
val jsonStringDs = spark.createDataset[String](
Seq(
("""{"firstname": "Sherlock", "lastname": "Holmes", "address": {"streetNumber": 121, "street": "Baker", "city": "London"}}"""),
("""{"name": "Amazon", "employeeCount": 500000, "marketCap": 817117000000, "revenue": 177900000000, "CEO": "Jeff Bezos"}""")))
jsonStringDs.show
jsonStringDs:org.apache.spark.sql.Dataset[String] = [value: string]
+----------------------------------------------------------------------------------------------------------------------+
|value
|
+----------------------------------------------------------------------------------------------------------------------+
|{"firstname": "Sherlock", "lastname": "Holmes", "address": {"streetNumber": 121, "street": "Baker", "city": "London"}}|
|{"name": "Amazon", "employeeCount": 500000, "marketCap": 817117000000, "revenue": 177900000000, "CEO": "Jeff Bezos"} |
+----------------------------------------------------------------------------------------------------------------------+
val df = spark.read.json(jsonStringDs)
df.show(false)
df:org.apache.spark.sql.DataFrame = [CEO: string, address: struct ... 6 more fields]
+----------+------------------+-------------+---------+--------+------------+------+------------+
|CEO |address |employeeCount|firstname|lastname|marketCap |name |revenue |
+----------+------------------+-------------+---------+--------+------------+------+------------+
|null |[London,Baker,121]|null |Sherlock |Holmes |null |null |null |
|Jeff Bezos|null |500000 |null |null |817117000000|Amazon|177900000000|
+----------+------------------+-------------+---------+--------+------------+------+------------+
该方法可从Spark 2.2.0获得:
这是一个想法,而不是一个完整的答案。您的意思是所有模式都共享一些公共密钥,而这些密钥正是您所关心的吗
Json
对象是可序列化的,因此您可以先创建一个RDD[Json]
,然后选择相关字段。(并非所有Argonaut对象都是可序列化的,因此您必须小心。或者选择不同的JSON库。)有几个常用键(示例中的键1、键2),但我关心变量键。公共键包含有关json的信息,如时间戳、源等,变量部分包含实际信息。您希望的输出是什么?一个数据帧,其列是所有相关键的并集(否则为null)?每个模式一个数据帧?我仍然认为您需要使用一些JSON库。你必须处理30个不同的案例,但我不认为你在任何情况下都可以避免——你有30种不同类型的数据。是的,我正在做30个DFs的“联合所有”。对于每个DF,我在字符串列中提取所需的键VAL。然而,我在一个UDF中尝试了“json镜头”库,它正在工作,但考虑到未来的支持和spark的内置优化,我想尝试spark的json函数。这种方法存在两个问题,1。spark需要很长时间来确定模式,因为我的输入数据很大。2.两个源可以在同一级别(在动态部分中)具有相同的键,但值可以是嵌套的json,在这种情况下,spark将推断模式直到公共key@Syntax,您将必须在您的输入文件中找出更改相同键的方法,抱歉,我帮不了什么忙。谢谢Ramesh的建议。很遗憾,我无法更改源数据。
case class JsonRow(key3_k2: String,key3_k1: String,key1: String,key2: String,key3_k3: String);scala.reflect.classTag[JsonRow].runtimeClass
def json(jsonDataset: Dataset[String]): DataFrame
val jsonStringDs = spark.createDataset[String](
Seq(
("""{"firstname": "Sherlock", "lastname": "Holmes", "address": {"streetNumber": 121, "street": "Baker", "city": "London"}}"""),
("""{"name": "Amazon", "employeeCount": 500000, "marketCap": 817117000000, "revenue": 177900000000, "CEO": "Jeff Bezos"}""")))
jsonStringDs.show
jsonStringDs:org.apache.spark.sql.Dataset[String] = [value: string]
+----------------------------------------------------------------------------------------------------------------------+
|value
|
+----------------------------------------------------------------------------------------------------------------------+
|{"firstname": "Sherlock", "lastname": "Holmes", "address": {"streetNumber": 121, "street": "Baker", "city": "London"}}|
|{"name": "Amazon", "employeeCount": 500000, "marketCap": 817117000000, "revenue": 177900000000, "CEO": "Jeff Bezos"} |
+----------------------------------------------------------------------------------------------------------------------+
val df = spark.read.json(jsonStringDs)
df.show(false)
df:org.apache.spark.sql.DataFrame = [CEO: string, address: struct ... 6 more fields]
+----------+------------------+-------------+---------+--------+------------+------+------------+
|CEO |address |employeeCount|firstname|lastname|marketCap |name |revenue |
+----------+------------------+-------------+---------+--------+------------+------+------------+
|null |[London,Baker,121]|null |Sherlock |Holmes |null |null |null |
|Jeff Bezos|null |500000 |null |null |817117000000|Amazon|177900000000|
+----------+------------------+-------------+---------+--------+------------+------+------------+