J'ai une base de données pyspark composée d'une colonne, appelée json
, où chaque ligne est une chaîne unicode de json. Je voudrais analyser chaque ligne et renvoyer une nouvelle image de données où chaque ligne est le json analysé.
# Sample Data Frame
jstr1 = u'{"header":{"id":12345,"foo":"bar"},"body":{"id":111000,"name":"foobar","sub_json":{"id":54321,"sub_sub_json":{"col1":20,"col2":"somethong"}}}}'
jstr2 = u'{"header":{"id":12346,"foo":"baz"},"body":{"id":111002,"name":"barfoo","sub_json":{"id":23456,"sub_sub_json":{"col1":30,"col2":"something else"}}}}'
jstr3 = u'{"header":{"id":43256,"foo":"foobaz"},"body":{"id":20192,"name":"bazbar","sub_json":{"id":39283,"sub_sub_json":{"col1":50,"col2":"another thing"}}}}'
df = sql_context.createDataFrame([Row(json=jstr1),Row(json=jstr2),Row(json=jstr3)])
J'ai essayé de mapper chaque ligne avec json.loads
:
(df
.select('json')
.rdd
.map(lambda x: json.loads(x))
.toDF()
).show()
Mais cela retourne un TypeError: expected string or buffer
Je suppose que le problème provient en partie du fait que, lors de la conversion d'un dataframe
en un rdd
, les informations de schéma sont perdues. J'ai donc également essayé de saisir manuellement les informations de schéma:
schema = StructType([StructField('json', StringType(), True)])
rdd = (df
.select('json')
.rdd
.map(lambda x: json.loads(x))
)
new_df = sql_context.createDataFrame(rdd, schema)
new_df.show()
Mais je reçois le même TypeError
.
En regardant cette réponse , il pourrait sembler utile d’aplanir les lignes avec flatMap
, mais je n’en ai pas le succès non plus:
schema = StructType([StructField('json', StringType(), True)])
rdd = (df
.select('json')
.rdd
.flatMap(lambda x: x)
.flatMap(lambda x: json.loads(x))
.map(lambda x: x.get('body'))
)
new_df = sql_context.createDataFrame(rdd, schema)
new_df.show()
Je reçois cette erreur: AttributeError: 'unicode' object has no attribute 'get'
.
Convertir une structure de données avec des chaînes json en structure de données structurée est en fait assez simple dans spark si vous convertissez la structure de données en RDD de chaînes avant (voir: http: //spark.Apache .org/docs/latest/sql-programming-guide.html # json-datasets )
Par exemple:
>>> new_df = sql_context.read.json(df.rdd.map(lambda r: r.json))
>>> new_df.printSchema()
root
|-- body: struct (nullable = true)
| |-- id: long (nullable = true)
| |-- name: string (nullable = true)
| |-- sub_json: struct (nullable = true)
| | |-- id: long (nullable = true)
| | |-- sub_sub_json: struct (nullable = true)
| | | |-- col1: long (nullable = true)
| | | |-- col2: string (nullable = true)
|-- header: struct (nullable = true)
| |-- foo: string (nullable = true)
| |-- id: long (nullable = true)
Pour Spark 2.1 + , vous pouvez utiliser from_json
qui permet la conservation des autres colonnes non json dans le cadre de données comme suit:
from pyspark.sql.functions import from_json, col
json_schema = spark.read.json(df.rdd.map(lambda row: row.json)).schema
df.withColumn('json', from_json(col('json'), json_schema))
Vous laissez Spark dériver le schéma de la colonne de chaîne json. Ensuite, le df.json
column n'est plus un StringType, mais la structure json correctement décodée, c'est-à-dire imbriquée StrucType
et toutes les autres colonnes de df
sont conservées telles quelles.
Vous pouvez accéder au contenu JSON comme suit:
df.select(col('json.header').alias('header'))
Les réponses existantes ne fonctionnent pas si votre JSON est autre chose que parfaitement/traditionnellement formaté. Par exemple, l'inférence de schéma basée sur RDD attend JSON entre accolades {}
et fournira un schéma incorrect (résultant en null
valeurs) si, par exemple, vos données ressemblent à:
[
{
"a": 1.0,
"b": 1
},
{
"a": 0.0,
"b": 2
}
]
J'ai écrit une fonction pour contourner ce problème en nettoyant JSON de telle sorte qu'il réside dans un autre objet JSON:
def parseJSONCols(df, *cols, sanitize=True):
"""Auto infer the schema of a json column and parse into a struct.
rdd-based schema inference works if you have well-formatted JSON,
like ``{"key": "value", ...}``, but breaks if your 'JSON' is just a
string (``"data"``) or is an array (``[1, 2, 3]``). In those cases you
can fix everything by wrapping the data in another JSON object
(``{"key": [1, 2, 3]}``). The ``sanitize`` option (default True)
automatically performs the wrapping and unwrapping.
The schema inference is based on this
`SO Post <https://stackoverflow.com/a/45880574)/>`_.
Parameters
----------
df : pyspark dataframe
Dataframe containing the JSON cols.
*cols : string(s)
Names of the columns containing JSON.
sanitize : boolean
Flag indicating whether you'd like to sanitize your records
by wrapping and unwrapping them in another JSON object layer.
Returns
-------
pyspark dataframe
A dataframe with the decoded columns.
"""
res = df
for i in cols:
# sanitize if requested.
if sanitize:
res = (
res.withColumn(
i,
psf.concat(psf.lit('{"data": '), i, psf.lit('}'))
)
)
# infer schema and apply it
schema = spark.read.json(res.rdd.map(lambda x: x[i])).schema
res = res.withColumn(i, psf.from_json(psf.col(i), schema))
# unpack the wrapped object if needed
if sanitize:
res = res.withColumn(i, psf.col(i).data)
return res
Remarque: psf
= pyspark.sql.functions
.
Voici une version concise (spark SQL) de la fonction parseJSONCols
de @ nolan-conaway.
SELECT
explode(
from_json(
concat('{"data":',
'[{"a": 1.0,"b": 1},{"a": 0.0,"b": 2}]',
'}'),
'data array<struct<a:DOUBLE, b:INT>>'
).data) as data;
PS J'ai aussi ajouté la fonction exploser: P
Vous aurez besoin de connaître certains types Hive SQL