Spark変数構造(ネストされたJSON)のJSONデータを処理するために使用しています。入力JSONデータは非常に大きく、1行あたりのキー数が1000を超え、1つのバッチが20を超える可能性がありますGB。バッチ全体が30個のデータソースから生成され、各JSONの 'key2'を使用してソースを識別でき、各ソースの構造は事前定義されています。
そのようなデータを処理するための最良のアプローチは何でしょうか?以下のようにfrom_jsonを使用してみましたが、これは固定スキーマでのみ機能し、最初にそれを使用するには、各ソースに基づいてデータをグループ化してからスキーマを適用する必要があります。データ量が多いため、データを1回だけスキャンし、事前定義されたスキーマに基づいて各ソースから必要な値を抽出することをお勧めします。
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])
これは、@ Ramesh Maharjanの回答を単に言い換えたものですが、より現代的なSpark構文を使用しています。
私はこのメソッドがDataFrameReader
に潜んでいることを発見しました。これにより、JSON文字列を_Dataset[String]
_から任意の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: http://spark.Apache.org/docs/2.2.0/api/scala/index.html#org.Apache .spark.sql.DataFrameReader @ json(jsonDataset:org.Apache.spark.sql.Dataset [String]):org.Apache.spark.sql.DataFrame
私の提案があなたに役立つかどうかはわかりませんが、同様のケースがあり、次のように解決しました:
1)つまり、json rapture(または他のjsonライブラリ)を使用して、JSONスキーマを動的にロードするという考えです。たとえば、jsonファイルの1行目を読んでスキーマを見つけることができます(ここで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と呼ばれるケースクラスを生成して、コードをより保守しやすくするという事実とは別に、シリアル化のパフォーマンスを向上させる強く型付けされたデータセットを用意することです。これを機能させるには、まずJsonRow.scalaファイルを作成する必要があります。次に、ソースファイルに基づいて動的にJsonRow.scala(もちろん複数ある場合があります)のコンテンツを変更するsbt事前ビルドスクリプトを実装する必要があります。クラス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
}
GenerateClassメソッドは、文字列のマップを受け入れて、クラスメンバーとクラス名自体を作成します。生成されたクラスのメンバーは、jsonスキーマから再度追加できます。
import org.codehaus.jackson.node.{ObjectNode, TextNode}
import collection.JavaConversions._
val mapping = collection.mutable.Map[String, String]()
val fields = json.$root.value.asInstanceOf[ObjectNode].getFields
for (f <- fields) {
(f.getKey, f.getValue) match {
case (k: String, v: TextNode) => mapping(k) = v.asText
case (k: String, v: ObjectNode) => v.getFields.foreach(f => mapping(f.getKey) = f.getValue.asText)
case _ => None
}
}
val dynClass = generateClass(mapping.toMap, "JsonRow")
println(dynClass)
これは出力します:
case class JsonRow(key3_k2: String,key3_k1: String,key1: String,key2: String,key3_k3: String);scala.reflect.classTag[JsonRow].runtimeClass
幸運を
質問で述べたようなデータがある場合
val data = sc.parallelize(
"""{"key1":"val1","key2":"source1","key3":{"key3_k1":"key3_v1"}}"""
:: Nil)
json dataに対してschema
を作成する必要はありません。 Spark sqlは、json文字列からschema
を推測できます。以下のようにSQLContext.read.json
を使用するだけです
val df = sqlContext.read.json(data)
上記で使用されたrddデータについて、以下のようにschema
が得られます
root
|-- key1: string (nullable = true)
|-- key2: string (nullable = true)
|-- key3: struct (nullable = true)
| |-- key3_k1: string (nullable = true)
そして、あなたは単にselect
key3_k1
として
df2.select("key3.key3_k1").show(false)
//+-------+
//|key3_k1|
//+-------+
//|key3_v1|
//+-------+
dataframe
は自由に操作できます。答えが役に立てば幸いです