web-dev-qa-db-fra.com

Comment convertir Row en json dans Spark 2 Scala

Existe-t-il un moyen simple de convertir un objet Row donné en json?

Vous avez découvert ceci au sujet de la conversion de toute une Dataframe en sortie json: Spark Row en JSON

Mais je veux juste convertir un one Row en json. Voici un pseudo code pour ce que j'essaie de faire.

Plus précisément, je lis json comme entrée dans un Dataframe . Je produis une nouvelle sortie basée principalement sur des colonnes, mais avec un champ json pour toutes les informations qui ne rentrent pas dans les colonnes.

Ma question est quel est le moyen le plus simple pour écrire cette fonction: 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)
  })
}

Les solutions de Psidom:

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

ne fonctionne que si la ligne n'a qu'un niveau, pas avec la ligne imbriquée. C'est le schéma: 

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

Aussi essayé suggestion Artem, mais cela n'a pas compilé:

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
}
7
Sami Badawi

J'ai besoin de lire l'entrée json et de produire une sortie json . La plupart des champs sont gérés individuellement, mais quelques sous-objets json doivent simplement être préservés.

Lorsque Spark lit une image, il transforme un enregistrement en ligne. The Row est un json comme structure. Cela peut être transformé et écrit en json. 

Mais je dois prendre des structures de sub json en une chaîne à utiliser comme nouveau champ.

Cela peut être fait comme ça:

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

location.address est le chemin d'accès à l'objet sous-json de la trame de données entrante basée sur json. address_json est le nom de colonne de cet objet converti en une version chaîne du json.

to_json est implémenté dans Spark 2.1.

Si vous générez une sortie json avec json4s, address_json doit être analysé selon une représentation AST, sinon la sortie json aura la partie address_json échappée.

3
Sami Badawi

Vous pouvez utiliser getValuesMap pour convertir l'objet de ligne en carte, puis le convertir en 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}
14
Psidom

Faites attention scala class scala.util.parsing.json.JSONObject est obsolète et ne supporte pas les valeurs null.

@deprecated ("Cette classe sera supprimée.", "2.11.0")

"JSONFormat.defaultFormat ne gère pas les valeurs NULL"

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

1
Arnon Rodman

Pour l'essentiel, vous pouvez avoir un cadre de données contenant une seule ligne. Ainsi, vous pouvez essayer de filtrer votre image de données initiale puis de l’analyser au format json.

1
Artem

JSon a un schéma mais Row n'a pas de schéma, vous devez donc appliquer le schéma sur Row et convertir en JSon. Voici comment vous pouvez le faire.

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

Je combine la suggestion de: Artem, KiranM et Psidom. A fait beaucoup de pistes et d’erreurs et a proposé cette solution que j’ai testée pour les structures imbriquées

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
}

Cette solution a fonctionné, mais uniquement en mode pilote.

0
Sami Badawi

J'ai eu le même problème, j'avais des fichiers parquet avec un schéma canonique (pas de tableaux), et je veux seulement obtenir des événements json J'ai fait comme suit, et cela semble bien fonctionner (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