flatten_spark_dataframe
A lightweight PySpark utility to recursively flatten deeply nested Spark DataFrames — automatically expanding StructType and ArrayType(StructType) columns into clean, top-level columns.
Why use this library?
Working with nested JSON data in PySpark is painful. A single deeply nested struct can require dozens of lines of manual col("a.b.c").alias(...) expressions, and arrays of structs need explicit explode() calls — all of which must be written by hand for every schema.
flatten_spark_dataframe solves this in one line:
| Without this library | With this library |
|---|---|
Manually write .select() / .withColumn() for every nested field |
One function call: flatten(df) |
| Must know the full schema upfront | Automatically discovers all nested columns |
| Exploding arrays requires separate steps | Arrays of structs are exploded + flattened automatically |
| Renaming nested fields is tedious | Clean parent_child naming convention applied automatically |
| No control over flattening depth | Control exactly how many levels to flatten |
| Columns you want nested stay nested? Manual filtering. | Pass an exclude_list to skip specific columns |
Installation
pip install flatten-spark-dataframe
Quick Start
import flatten_spark_dataframe
# Flatten everything (all levels)
flat_df = flatten_spark_dataframe.flatten(df)
# Flatten only 1 level deep
flat_df = flatten_spark_dataframe.flatten(df, flatten_till_level=1)
# Exclude specific columns from flattening
flat_df = flatten_spark_dataframe.flatten(df, exclude_list=["address", "metadata"])
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
df |
DataFrame | (required) | The input PySpark DataFrame with nested columns |
flatten_till_level |
'complete' or int |
'complete' |
'complete' flattens all levels; an integer limits the depth (e.g., 1 = one level only) |
exclude_list |
list[str] |
[] |
Column names (lowercase) to skip — these are kept nested in the output |
Detailed Example
Sample Data (3 levels of nesting)
The schema below has struct inside struct (name.firstname.initial) and a top-level struct (country):
root
|-- name: struct
| |-- firstname: struct ← Level 1
| | |-- initial: string ← Level 2
| | |-- actualname: string ← Level 2
| |-- middlename: string ← Level 1
| |-- lastname: string ← Level 1
|-- state: string
|-- gender: string
|-- country: struct
| |-- city: string ← Level 1
| |-- street: string ← Level 1
from pyspark.sql.types import StructType, StructField, StringType
data = [
((("A", "James"), None, "Smith"), "OH", "M", ("F", "Mike")),
((("B", "Anna"), "Rose", ""), "NY", "F", ("E", "Jen")),
((("C", "Julia"), "", "Williams"), "OH", "F", ("D", "Maria")),
((("D", "Maria"), "Anne", "Jones"), "NY", "M", ("C", "Julia")),
((("E", "Jen"), "Mary", "Brown"), "NY", "M", ("B", "Anna")),
((("F", "Mike"), "Mary", "Williams"), "OH", "M", ("A", "James")),
]
schema = StructType([
StructField('name', StructType([
StructField('firstname', StructType([
StructField('initial', StringType(), True),
StructField('actualname', StringType(), True),
])),
StructField('middlename', StringType(), True),
StructField('lastname', StringType(), True),
])),
StructField('state', StringType(), True),
StructField('gender', StringType(), True),
StructField('country', StructType([
StructField('city', StringType(), True),
StructField('street', StringType(), True),
])),
])
df = spark.createDataFrame(data=data, schema=schema)
Example 1: Flatten completely (all levels)
import flatten_spark_dataframe
flat_df = flatten_spark_dataframe.flatten(df)
flat_df.show()
What happens internally:
- Level 1 —
nameis expanded toname_firstname(still a struct),name_middlename,name_lastname.countryis expanded tocountry_city,country_street. - Level 2 —
name_firstnameis expanded toname_firstname_initial,name_firstname_actualname.
Output:
+-----+------+---------------+-------------+------------+--------------+----------------------+--------------------------+
|state|gender|name_middlename|name_lastname|country_city|country_street|name_firstname_initial |name_firstname_actualname |
+-----+------+---------------+-------------+------------+--------------+----------------------+--------------------------+
| OH| M| null| Smith| F| Mike| A | James|
| NY| F| Rose| | E| Jen| B | Anna|
| OH| F| | Williams| D| Maria| C | Julia|
| NY| M| Anne| Jones| C| Julia| D | Maria|
| NY| M| Mary| Brown| B| Anna| E | Jen|
| OH| M| Mary| Williams| A| James | F | Mike|
+-----+------+---------------+-------------+------------+--------------+----------------------+--------------------------+
All nested structs have been fully flattened into 8 top-level columns.
Example 2: Flatten only 1 level deep
flat_df_l1 = flatten_spark_dataframe.flatten(df, flatten_till_level=1)
flat_df_l1.printSchema()
Output schema:
root
|-- state: string
|-- gender: string
|-- name_firstname: struct ← Still nested (would need level 2 to expand)
| |-- initial: string
| |-- actualname: string
|-- name_middlename: string ← Flattened from name.middlename
|-- name_lastname: string ← Flattened from name.lastname
|-- country_city: string ← Flattened from country.city
|-- country_street: string ← Flattened from country.street
Only the first level of structs is expanded. name_firstname remains a struct because it was at level 2.
Example 3: Flatten with exclusions
flat_df_excl = flatten_spark_dataframe.flatten(df, exclude_list=["country"])
flat_df_excl.printSchema()
Output schema:
root
|-- country: struct ← Kept nested (excluded)
| |-- city: string
| |-- street: string
|-- state: string
|-- gender: string
|-- name_middlename: string
|-- name_lastname: string
|-- name_firstname_initial: string
|-- name_firstname_actualname: string
The country struct is preserved as-is while everything else is fully flattened.
Example 4: Combine level control + exclusions
flat_df_combo = flatten_spark_dataframe.flatten(df, flatten_till_level=1, exclude_list=["country"])
flat_df_combo.printSchema()
Output schema:
root
|-- country: struct ← Excluded — kept nested
| |-- city: string
| |-- street: string
|-- state: string
|-- gender: string
|-- name_firstname: struct ← Level 2 — not flattened (limit = 1)
| |-- initial: string
| |-- actualname: string
|-- name_middlename: string
|-- name_lastname: string
How it works
- Classifies columns into flat (primitives), struct, and array-of-struct categories
- Expands structs into sub-fields using
parent_childnaming (special characters are cleaned) - Explodes arrays of structs using
explode_outer()(preserves rows even when the array is null/empty) - Recurses until all levels are flattened or the depth limit is reached
- Handles duplicates — if a flattened field name collides with an existing column, a suffix is appended
License
Metadata
Release files for flatten-spark-dataframe 0.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| flatten_spark_dataframe-0.0.2.tar.gz | 7.9 kB | Without provenance | Details for flatten_spark_dataframe-0.0.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| flatten_spark_dataframe-0.0.2-py3-none-any.whl | Python 3 | none | any | Details for flatten_spark_dataframe-0.0.2-py3-none-any.whl |
Total release size: 15.8 kB
Release files / flatten_spark_dataframe-0.0.2.tar.gz
| Download URL | flatten_spark_dataframe-0.0.2.tar.gz |
|---|---|
| Size | 7.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fcc95a1772516e0157f8cfa50a09d252c094d932b05904f33e16c1646c8a2aab
|
|
BLAKE2b-256 checksum How to use checksums |
60c8fd8d3b8e342e4d7db2c78ed46bd6e1273b9eebe31aa19d746b443c05ec42
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.9
|
Release files / flatten_spark_dataframe-0.0.2-py3-none-any.whl
| Download URL | flatten_spark_dataframe-0.0.2-py3-none-any.whl |
|---|---|
| Size | 7.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f9f69627f798f81bbfacdcfb2236846e20b657ce9b4fc8e276a4b2bfb395ef59
|
|
BLAKE2b-256 checksum How to use checksums |
ff05d9b862be4e21dc948ddf3faaacf1fb842f748555818a7767bf86bf4e1014
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.9
|
Security for flatten-spark-dataframe 0.0.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.