web-dev-qa-db-ja.com

Spark 2 Scalaで行をjsonに変換する方法

特定のRowオブジェクトをJSONに変換する簡単な方法はありますか?

Dataframe全体をjson出力に変換することについてこれを見つけました: Spark Row to JSON

ただし、1行をjsonに変換したいだけです。ここに私がやろうとしていることの擬似コードがあります。

より正確には、jsonをDataframeの入力として読み取ります。主に列に基づく新しい出力を作成していますが、列に収まらないすべての情報に対して1つのjsonフィールドを使用しています。

私の質問、この関数を書く最も簡単な方法は何ですか:convertRowToJson()

def convertRowToJson(row: Row): String = ???

def transformVenueTry(row: Row): Try[Venue] = {
  Try({
    val name = row.getString(row.fieldIndex("name"))
    val metadataRow = row.getStruct(row.fieldIndex("meta"))
    val score: Double = calcScore(row)
    val combinedRow: Row = metadataRow ++ ("score" -> score)
    val jsonString: String = convertRowToJson(combinedRow)
    Venue(name = name, json = jsonString)
  })
}

Psidomのソリューション:

def convertRowToJSON(row: Row): String = {
    val m = row.getValuesMap(row.schema.fieldNames)
    JSONObject(m).toString()
}

行がネストされた行ではなく、1つのレベルしかない場合にのみ機能します。これはスキーマです:

StructType(
    StructField(indicator,StringType,true),   
    StructField(range,
    StructType(
        StructField(currency_code,StringType,true),
        StructField(maxrate,LongType,true), 
        StructField(minrate,LongType,true)),true))

Artemの提案も試みましたが、コンパイルされませんでした:

def row2DataFrame(row: Row, sqlContext: SQLContext): DataFrame = {
  val sparkContext = sqlContext.sparkContext
  import sparkContext._
  import sqlContext.implicits._
  import sqlContext._
  val rowRDD: RDD[Row] = sqlContext.sparkContext.makeRDD(row :: Nil)
  val dataFrame = rowRDD.toDF() //XXX does not compile
  dataFrame
}
8
Sami Badawi

JSON入力を読み取り、JSON出力を生成する必要があります。ほとんどのフィールドは個別に処理されますが、いくつかのjsonサブオブジェクトを保持する必要があります。

Sparkは、データフレームを読み取ると、レコードを行に変換します。行は、jsonに似た構造です。変換してjsonに書き出すことができます。

しかし、新しいフィールドとして使用するには、いくつかのサブjson構造を文字列に取り出す必要があります。

これは次のように実行できます。

dataFrameWithJsonField = dataFrame.withColumn("address_json", to_json($"location.address"))

location.addressは、着信jsonベースのデータフレームのサブjsonオブジェクトへのパスです。 address_jsonは、jsonの文字列バージョンに変換されたオブジェクトの列名です。

to_jsonは、Spark 2.1。

Json4sを使用して出力JSONを生成する場合、address_jsonをAST表現に解析する必要があります。そうでない場合、出力jsonのaddress_json部分はエスケープされます。

3
Sami Badawi

getValuesMapを使用して、行オブジェクトをマップに変換してからJSONに変換できます。

import scala.util.parsing.json.JSONObject
import org.Apache.spark.sql._

val df = Seq((1,2,3),(2,3,4)).toDF("A", "B", "C")    
val row = df.first()          // this is an example row object

def convertRowToJSON(row: Row): String = {
    val m = row.getValuesMap(row.schema.fieldNames)
    JSONObject(m).toString()
}

convertRowToJSON(row)
// res46: String = {"A" : 1, "B" : 2, "C" : 3}
18
Psidom

注意してくださいscala class scala.util.parsing.json.JSONObjectは非推奨であり、null値をサポートしていません。

@deprecated( "このクラスは削除されます。"、 "2.11.0")

「JSONFormat.defaultFormatはnull値を処理しません」

https://issues.scala-lang.org/browse/SI-5092

3
Arnon Rodman

基本的に、1行のみを含むデータフレームを作成できます。したがって、初期データフレームをフィルタリングしてから、jsonに解析することができます。

1
Artem

JSonにはスキーマがありますが、Rowにはスキーマがないため、Rowにスキーマを適用してJSonに変換する必要があります。方法は次のとおりです。

import org.Apache.spark.sql.Row
import org.Apache.spark.sql.types._

def convertRowToJson(row: Row): String = {

  val schema = StructType(
      StructField("name", StringType, true) ::
      StructField("meta", StringType, false) ::  Nil)

      return sqlContext.applySchema(row, schema).toJSON
}
1
KiranM

Artem、KiranM、Psidomからの提案を組み合わせます。多くのトレイルとエラーが発生し、ネストされた構造についてテストしたこのソリューションを思い付きました。

def row2Json(row: Row, sqlContext: SQLContext): String = {
  import sqlContext.implicits
  val rowRDD: RDD[Row] = sqlContext.sparkContext.makeRDD(row :: Nil)
  val dataframe = sqlContext.createDataFrame(rowRDD, row.schema)
  dataframe.toJSON.first
}

このソリューションは機能しましたが、ドライバーモードで実行しているときにのみ有効でした。

0
Sami Badawi

私は同じ問題を抱えていました。標準的なスキーマ(配列なし)の寄木細工のファイルがあり、jsonイベントのみを取得したいのです。私は次のようにしましたが、うまくいくようです(Spark 2.1):

import org.Apache.spark.sql.types.StructType
import org.Apache.spark.sql.{DataFrame, Dataset, Row}
import scala.util.parsing.json.JSONFormat.ValueFormatter
import scala.util.parsing.json.{JSONArray, JSONFormat, JSONObject}

def getValuesMap[T](row: Row, schema: StructType): Map[String,Any] = {
  schema.fields.map {
    field =>
      try{
        if (field.dataType.typeName.equals("struct")){
          field.name -> getValuesMap(row.getAs[Row](field.name),   field.dataType.asInstanceOf[StructType]) 
        }else{
          field.name -> row.getAs[T](field.name)
        }
      }catch {case e : Exception =>{field.name -> null.asInstanceOf[T]}}
  }.filter(xy => xy._2 != null).toMap
}

def convertRowToJSON(row: Row, schema: StructType): JSONObject = {
  val m: Map[String, Any] = getValuesMap(row, schema)
  JSONObject(m)
}
//I guess since I am using Any and not nothing the regular ValueFormatter is not working, and I had to add case jmap : Map[String,Any] => JSONObject(jmap).toString(defaultFormatter)
val defaultFormatter : ValueFormatter = (x : Any) => x match {
  case s : String => "\"" + JSONFormat.quoteString(s) + "\""
  case jo : JSONObject => jo.toString(defaultFormatter)
  case jmap : Map[String,Any] => JSONObject(jmap).toString(defaultFormatter)
  case ja : JSONArray => ja.toString(defaultFormatter)
  case other => other.toString
}

val someFile = "s3a://bucket/file"
val df: DataFrame = sqlContext.read.load(someFile)
val schema: StructType = df.schema
val jsons: Dataset[JSONObject] = df.map(row => convertRowToJSON(row, schema))
0
Ehud Lev