AptoCompass Logo
Apto Compass

How to Convert Nested JSON to CSV (Python, CLI, and Free Browser Utility)

Step-by-step guide to converting complex nested JSON files and API responses to tabular CSV spreadsheets using Python Pandas, jq, standard library, and a client-side tool.

By Abhinay Kumar•14 min read•Updated: 2026-10-28
JSON to CSV Conversion Tutorial and Code Examples

Quick Summary & Key Takeaways

  • Transform hierarchical JSON objects into flat CSV spreadsheets using Python Pandas json_normalize or recursive standard library scripts.
  • Unpack nested arrays of line items into individual table rows while preserving parent identifiers through explicit record path mapping.
  • Stream multi-gigabyte JSON Lines and server logs directly into CSV format using terminal jq filters without running out of RAM.
  • Convert sensitive customer payloads securely using client-side browser utilities that execute entirely in local memory with zero server uploads.

Free Interactive Companion Tool

Test inputs and calculate figures directly with our in-browser tool

Launch Tool

How to Convert Nested JSON to CSV (Python, CLI, and Free Browser Utility)

Converting hierarchical JSON payloads into flat, two-dimensional CSV spreadsheets requires flattening nested objects and unpacking child arrays. This guide covers four production methods - from a private client-side browser converter to Python Pandas and command-line jq scripts - with complete code and expected output.

Fast Solution: Convert Nested JSON in Three Lines of Python

In Python, load your data into Pandas using pd.json_normalize(). This flattens child objects and unpacks nested arrays into tabular rows:

import pandas as pd
df = pd.json_normalize(data, record_path=['items'], meta=['order_id', ['customer', 'name']], sep='_')
df.to_csv('output.csv', index=False)

If you need to convert an API response or log file immediately without installing packages or uploading private records to an external server, use our client-side JSON to CSV Converter. It runs entirely in your web browser with zero server data transfer.


Method Comparison Matrix

The table below summarizes five reliable ways to transform JSON payloads into tabular CSV files based on schema depth, file size, and execution requirements:

Method Best Use Case Nested Object Flattening Array of Objects Unpacking Max Recommended File Size Setup Required Offline / Privacy Safe Scriptable / Automatable
AptoCompass Web Tool Ad-hoc conversions, quick API tests, non-programmers Automatic (dot notation) Preserves JSON string in cell Up to 25 MB Zero (Runs in browser) 100% Private (Client-side only) No (Interactive UI)
Python Pandas (json_normalize) Analytics pipelines, complex schema normalization Automatic (sep='.' or sep='_') Built-in via record_path and meta Up to 500 MB (RAM dependent) Medium (pip install pandas) 100% Local / Private Yes (Python script)
Python Standard Library Minimal Docker containers, AWS Lambda, zero dependencies Requires recursive helper function Requires custom loop logic Up to 100 MB Zero (Built into Python 3) 100% Local / Private Yes (Python script)
Command Line (jq) Shell scripts, log processing, curl pipelines Manual via dot notation in query Unwinds via array iterator (.[]) Multi-GB (Streaming) Low (brew install jq) 100% Local / Private Yes (Bash / Zsh script)
Command Line (in2csv) Quick conversion of flat tabular JSON No (Flat data only) No (Fails on nested arrays) Up to 100 MB Low (pip install csvkit) 100% Local / Private Yes (Bash / Zsh script)

Prerequisites and Environment Setup

Before executing the programmatic examples in this tutorial, ensure your local development environment meets these baseline specifications:

  • Python 3.10 or later: Verify your version by running python3 --version in your terminal.
  • Pandas Library: Required for Steps 2, 3, and 6. Install it in your virtual environment with pip install pandas.
  • jq Utility: Required for Step 4. Install it on macOS using brew install jq, or on Ubuntu/Debian using sudo apt-get install jq.
  • Sample Dataset: Create a test file named orders.json containing hierarchical records with customer objects, nested billing addresses, and line item arrays to follow along.

Step 1: Convert Instantly Using a Client-Side Browser Tool

The browser utility parses raw JSON strings into an in-memory document tree and flattens child attributes into dot-notation column headers without transmitting data over the network. This local execution model satisfies corporate data protection rules because customer records, payment details, and authentication tokens never leave your computer.

If your payload contains syntax errors or unescaped characters from an API dump, validate and inspect the formatting first using our browser-based JSON formatter. Once valid, paste your JSON array into the converter:

[
  {
    "order_id": "ORD-9481",
    "customer": {
      "name": "Sarah Chen",
      "address": {
        "city": "Austin",
        "state": "TX"
      }
    },
    "payment": {
      "amount": 149.50,
      "currency": "USD"
    },
    "status": "completed"
  }
]

Select your preferred delimiter (comma, semicolon, or tab), click Convert, and download the resulting CSV spreadsheet.

Expected output:

order_id,customer.name,customer.address.city,customer.address.state,payment.amount,payment.currency,status
ORD-9481,Sarah Chen,Austin,TX,149.5,USD,completed

Step 2: Flatten Nested Objects Using Python Pandas

The pd.json_normalize() function inspects nested dictionary trees and expands child attributes into compound column names using a separator character. This eliminates the common defect where child objects become unparsed strings inside spreadsheet cells.

Save this script as flatten_objects.py:

import json
import pandas as pd

# Load the hierarchical JSON payload
with open('orders.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

# Flatten nested dictionaries using '_' separator
df = pd.json_normalize(data, sep='_')

# Write clean 2D table to CSV
df.to_csv('orders_flat.csv', index=False)
print(f"Successfully converted {len(df)} records.")
print(df[['order_id', 'customer_name', 'customer_address_city', 'payment_amount']].head())

Run the script from your terminal:

python3 flatten_objects.py

Expected output:

Successfully converted 2 records.
   order_id   customer_name customer_address_city  payment_amount
0  ORD-9481      Sarah Chen                Austin           149.5
1  ORD-9482    Marcus Vance                Denver            89.0

Step 3: Unpack Nested Arrays and Line Items with record_path and meta

Hierarchical JSON files often store one-to-many relationships, such as an order containing a list of purchased products. A standard spreadsheet row cannot store an array without data corruption. The record_path parameter unpacks each item in the child array into a distinct row, while the meta parameter duplicates the parent order attributes across every row.

Save this script as unpack_arrays.py:

import json
import pandas as pd

payload = [
    {
        "order_id": "ORD-9481",
        "customer": {
            "name": "Sarah Chen",
            "city": "Austin"
        },
        "items": [
            {
                "sku": "KEY-WL-01",
                "product_name": "Wireless Mechanical Keyboard",
                "quantity": 1,
                "price": 120.00
            },
            {
                "sku": "MAT-XL-02",
                "product_name": "Desk Mat",
                "quantity": 1,
                "price": 29.50
            }
        ]
    },
    {
        "order_id": "ORD-9482",
        "customer": {
            "name": "Marcus Vance",
            "city": "Denver"
        },
        "items": [
            {
                "sku": "MOU-ER-09",
                "product_name": "Ergonomic Vertical Mouse",
                "quantity": 1,
                "price": 89.00
            }
        ]
    }
]

# Unpack items list into distinct rows and attach parent metadata
df = pd.json_normalize(
    payload,
    record_path=['items'],
    meta=['order_id', ['customer', 'name'], ['customer', 'city']],
    sep='_'
)

# Reorder columns for clean accounting presentation
column_order = [
    'order_id',
    'customer_name',
    'customer_city',
    'sku',
    'product_name',
    'quantity',
    'price'
]
df = df[column_order]
df.to_csv('line_items.csv', index=False)
print(df.to_string(index=False))

Run the script:

python3 unpack_arrays.py

Expected output:

order_id customer_name customer_city       sku                  product_name  quantity  price
ORD-9481    Sarah Chen        Austin KEY-WL-01 Wireless Mechanical Keyboard         1  120.0
ORD-9481    Sarah Chen        Austin MAT-XL-02                      Desk Mat         1   29.5
ORD-9482  Marcus Vance        Denver MOU-ER-09      Ergonomic Vertical Mouse         1   89.0

Step 4: Stream and Convert from the Command Line Using jq

The jq command-line processor filters structured text in memory and serializes key-value arrays directly through its built-in @csv filter. This utility runs on Linux, macOS, and WSL, making it suitable for automated shell scripts and CI/CD cron jobs.

Execute this single command to extract nested fields and output RFC-compliant CSV with header labels:

jq -r '
  ["order_id", "customer_name", "customer_city", "sku", "product_name", "quantity", "price"],
  (.[] | . as $parent | .items[] | [
    $parent.order_id,
    $parent.customer.name,
    $parent.customer.city,
    .sku,
    .product_name,
    .quantity,
    .price
  ])
  | @csv
' sample_line_items.json > orders_cli.csv

Inspect the generated CSV file using cat:

cat orders_cli.csv

Expected output:

"order_id","customer_name","customer_city","sku","product_name","quantity","price"
"ORD-9481","Sarah Chen","Austin","KEY-WL-01","Wireless Mechanical Keyboard",1,120
"ORD-9481","Sarah Chen","Austin","MAT-XL-02","Desk Mat",1,29.5
"ORD-9482","Marcus Vance","Denver","MOU-ER-09","Ergonomic Vertical Mouse",1,89

Step 5: Convert with Pure Python Standard Library

Minimal container environments and AWS Lambda functions often lack third-party packages like Pandas. A recursive helper function flattens child dictionaries into dot-delimited keys, and the built-in Python csv module writes rows using csv.DictWriter without third-party dependencies.

Save this script as standard_lib_converter.py:

import csv
import json

def flatten_dictionary(d, parent_key='', sep='.'):
    """Recursively flattens nested dictionaries into compound keys."""
    items = []
    for k, v in d.items():
        new_key = f"{parent_key}{sep}{k}" if parent_key else k
        if isinstance(v, dict):
            items.extend(flatten_dictionary(v, new_key, sep=sep).items())
        else:
            items.append((new_key, v))
    return dict(items)

def convert_json_file_to_csv(input_path, output_path):
    with open(input_path, 'r', encoding='utf-8') as f:
        records = json.load(f)

    # Flatten every record in the array
    flattened_rows = [flatten_dictionary(record) for record in records]

    # Pre-scan all records to detect every distinct key (handles schema drift)
    fieldnames = []
    for row in flattened_rows:
        for key in row.keys():
            if key not in fieldnames:
                fieldnames.append(key)

    # Write rows to CSV with proper quoting
    with open(output_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
        writer.writeheader()
        writer.writerows(flattened_rows)

    print(f"Wrote {len(flattened_rows)} rows to {output_path} across {len(fieldnames)} columns.")

if __name__ == '__main__':
    convert_json_file_to_csv('orders.json', 'orders_stdlib.csv')

Execute the script:

python3 standard_lib_converter.py

Expected output:

Wrote 2 rows to orders_stdlib.csv across 10 columns.

Step 6: Stream Multi-Gigabyte JSON Lines Files Without Memory Crashes

Loading a 500MB JSON document into memory with json.load() can consume 2GB to 4GB of RAM because Python dictionary pointers carry high overhead. Streaming newline-delimited JSON (.jsonl or NDJSON) in sequential chunks keeps resident memory usage below 50MB regardless of total file size.

Save this script as stream_json_lines.py:

import pandas as pd

input_file = 'server_logs.jsonl'
output_file = 'server_logs.csv'
chunk_size = 10000

# Initialize an empty file with headers on the first iteration
is_first_chunk = True

for chunk in pd.read_json(input_file, lines=True, chunksize=chunk_size):
    # Normalize nested attributes if present in the chunk
    flattened_chunk = pd.json_normalize(chunk.to_dict(orient='records'), sep='_')
    
    # Append chunk to the destination CSV
    flattened_chunk.to_csv(
        output_file,
        mode='w' if is_first_chunk else 'a',
        header=is_first_chunk,
        index=False
    )
    is_first_chunk = False
    print(f"Processed chunk of {len(chunk)} records...")

print("Streaming conversion complete.")

Run the streaming job:

python3 stream_json_lines.py

Expected output:

Processed chunk of 10000 records...
Processed chunk of 10000 records...
Processed chunk of 10000 records...
Streaming conversion complete.

Before and After Worked Example: E-Commerce Webhook to CSV

The following worked example demonstrates an end-to-end transformation of an e-commerce webhook payload containing billing records, customer metadata, and purchased line items into an accounting spreadsheet.

Input: Hierarchical JSON Payload (webhook_payload.json)

[
  {
    "event_id": "evt_1092847",
    "timestamp": "2026-10-28T14:32:00Z",
    "customer": {
      "id": "cus_9941",
      "email": "[email protected]",
      "billing_address": {
        "city": "Austin",
        "state": "TX",
        "postal_code": "78701"
      }
    },
    "cart": {
      "items": [
        {
          "sku": "KEY-WL-01",
          "name": "Wireless Mechanical Keyboard",
          "unit_price": 120.00,
          "qty": 1
        },
        {
          "sku": "MAT-XL-02",
          "name": "Desk Mat XXL",
          "unit_price": 29.50,
          "qty": 2
        }
      ]
    },
    "tax_total": 14.32,
    "payment_status": "succeeded"
  }
]

Transformation Script

import json
import pandas as pd

with open('webhook_payload.json', 'r', encoding='utf-8') as f:
    payload = json.load(f)

# Unpack cart items and merge transaction metadata
normalized_df = pd.json_normalize(
    payload,
    record_path=['cart', 'items'],
    meta=[
        'event_id',
        'timestamp',
        ['customer', 'id'],
        ['customer', 'email'],
        ['customer', 'billing_address', 'city'],
        ['customer', 'billing_address', 'state'],
        'tax_total',
        'payment_status'
    ],
    sep='_'
)

# Rename columns for clarity
normalized_df.rename(columns={
    'customer_billing_address_city': 'city',
    'customer_billing_address_state': 'state'
}, inplace=True)

normalized_df.to_csv('webhook_accounting.csv', index=False)

If you are standardizing JSON data models for web applications or search engine indexing, our companion JSON-LD schema markup guide provides step-by-step methods for structuring hierarchical schemas before exporting them to tabular formats.

Output: Tabular CSV Spreadsheet (webhook_accounting.csv)

sku,name,unit_price,qty,event_id,timestamp,customer_id,customer_email,city,state,tax_total,payment_status
KEY-WL-01,Wireless Mechanical Keyboard,120.0,1,evt_1092847,2026-10-28T14:32:00Z,cus_9941,[email protected],Austin,TX,14.32,succeeded
MAT-XL-02,Desk Mat XXL,29.5,2,evt_1092847,2026-10-28T14:32:00Z,cus_9941,[email protected],Austin,TX,14.32,succeeded

Each product now occupies an independent row with full order context, allowing financial analysts to calculate sums, filter by SKU, or build pivot tables in Excel without manual cleaning.


Common Gotchas and Troubleshooting

1. Inconsistent Schemas and Missing Keys Across Records

Problem: Standard Python scripts fail with KeyError, or the generated CSV omits columns present in later records.
Cause: Unlike relational databases, JSON document stores allow schema drift. If record #1 contains ['id', 'name'] and record #2 contains ['id', 'name', 'phone'], a script that reads keys only from the first row will ignore phone entirely.
Fix: Pre-scan every record in the array to build a master set of all field names before initializing csv.DictWriter, or use pd.json_normalize(), which automatically aligns keys and inserts NaN for absent attributes.

2. Process Killed by Memory Allocation on Large Files

Problem: Python terminates with MemoryError, or a terminal process displays Killed: 9 when opening files above 150MB.
Cause: Standard parsing functions like json.load() build full syntax trees in RAM. A 200MB JSON file often expands to 1.5GB of heap memory.
Fix: Convert the source dataset into JSON Lines format (.jsonl), then process it using the streaming generator in Step 6 or pipe it directly through jq.

3. Nested Arrays Exported as Raw Text Strings

Problem: Child arrays appear inside single CSV cells as raw strings like [{'sku': 'A1'}, {'sku': 'B2'}] or JavaScript [object Object].
Cause: Standard flatteners only traverse dictionary key-value pairs. When encountering a list, they write the raw list object into the cell.
Fix: Use pd.json_normalize() with the record_path parameter as shown in Step 3, or use jq array unpacking (.items[]) to turn list elements into individual rows.

4. Delimiter Collisions and Unquoted Commas in Addresses

Problem: Address fields containing commas (such as "Austin, TX") split into two different columns in Excel, corrupting table alignment.
Cause: The CSV exporter failed to apply quoting rules defined in RFC 4180.
Fix: In Python, set quoting=csv.QUOTE_MINIMAL (the default in csv.DictWriter) or use pd.to_csv(), which quotes text fields containing commas automatically.


Verification Protocol

Run these verification checks to confirm your generated CSV file is properly formatted and syntactically valid:

1. Check Row Count and Column Headers

Inspect the first three lines and total line count using standard terminal utilities:

head -n 3 orders_flat.csv
wc -l orders_flat.csv

Verify that line 1 contains comma-separated column names without trailing commas, and that the total line count equals your records plus one header line.

2. Automated Python Integrity Check

Run this short script to assert that no rows are missing and that nested keys were flattened properly:

import csv

with open('orders_flat.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f)
    header = next(reader)
    rows = list(reader)

# Assertions
assert len(header) >= 4, f"Expected at least 4 columns, got {len(header)}"
assert 'customer_name' in header or 'customer.name' in header, "Missing flattened customer name column"
assert len(rows) > 0, "CSV file contains zero data rows"

print("Integrity check passed: CSV structure is valid and properly flattened.")

Expected output:

Integrity check passed: CSV structure is valid and properly flattened.

Editor's Note

  • Environment Assumptions: This guide assumes Python 3.10 or later on Linux, macOS, or Windows WSL. Code examples were verified with Python 3.14 and jq 1.7.
  • Library Versions: Pandas snippets use modern top-level pd.json_normalize(). The legacy pandas.io.json.json_normalize() function was deprecated in Pandas 1.0 and has been excluded.
  • Privacy Standards: The AptoCompass JSON to CSV converter referenced in Step 1 processes data 100% in client-side browser memory via Web Workers. No payloads are sent to remote servers.

Frequently Asked Questions