If you’ve ever pulled an order feed from Shopify, a product catalog from an API, or analytics from a marketplace and tried to open it in Excel, you know the pain: half the fields show up as [object Object] or get silently dropped. Nested JSON and spreadsheets are fundamentally different data shapes, and naive converters lose data every time. This guide walks you through converting nested JSON to Excel properly with Python and pandas — flattening objects, exploding arrays, and preserving the parent-child relationships you’d otherwise destroy.
By the end, you’ll have a complete, reusable script you can point at any nested JSON file.
Why Nested JSON Can’t Be Represented as One Table
Excel is a two-dimensional grid: rows and columns, one value per cell. JSON has no such constraint. A single order record can contain a customer object, an array of line items, each line item with its own array of discounts. That’s a tree, not a table.
When you force a tree into one grid, you face three structural problems:
- Nested objects (
order.customer.email) need flattening into dot-notated columns. - Arrays (
order.line_items[]) need either one row per element or serialization into a single cell. - Mixed depth — some records have three line items, others have zero — breaks any fixed column layout.
Online converters typically handle problem #1 and ignore #2 entirely, which is how you end up with an Excel file missing 40% of your order data. The fix is deliberate flattening, not blind conversion.
Sample Nested JSON File
Save this as orders.json. We’ll use it throughout — it contains every problem you’ll hit in the wild: nested objects, arrays, missing keys, and empty lists.
Notice ORD-1002 is missing email, address, and has an empty line_items array. Any robust pipeline must survive this.
What You Need
- Python 3.10 or newer
pandasandopenpyxlinstalled:pip install pandas openpyxl- Your JSON file (array-of-objects format works best; we’ll handle line-delimited JSON too)
Step 1: Flatten Objects with pandas.json_normalize()
pandas.json_normalize() is the workhorse for nested objects. It walks the tree and produces dot-notated columns.
Output columns:
Two important choices here:
sep="_"instead of the default"."— dots in column names cause headaches later if you ever push this data to SQL or back to JSON.- Missing keys (
customer_emailfor ORD-1002) becomeNaN, not errors. That’s what you want.
But look at line_items — it’s still a list of dicts sitting inside a single cell. That’s Step 2’s job.
Step 2: Handle Arrays with explode()
You have two legitimate strategies for arrays, and picking the wrong one is the most common data-loss bug:
For anything you’d want to sum, filter, or pivot in Excel, explode. Here’s the full pattern:
The record_path / meta combo is the key insight most tutorials skip: meta preserves parent context on every exploded child row. Each line item row carries its order_id, so you can always join back.
Caveat: orders with empty line_items arrays (ORD-1002) vanish from this output. If you need them, do a second pass on the parent-level dataframe for rows where the array is empty and append placeholder rows.
Step 3: Recursive Flattening for Deeply Irregular JSON
json_normalize struggles when your JSON is deeply nested and irregular — think product feeds where attributes vary per category. A recursive flattener gives you total control:
This approach shines when you need custom rules — e.g., truncating keys deeper than four levels, or serializing lists under a certain key but exploding others. It’s also easier to debug: wrap the loop in a try/except and log the offending key path.
Step 4: Export with to_excel()
Export both tables — parent orders and exploded line items — to separate sheets in one workbook. This mirrors the JSON’s natural structure and is far more useful than a single denormalized sheet:
Three Excel-specific gotchas:
- Timezone-aware datetimes throw
ValueError: Excel does not support datetimes with timezones. Strip them first:df["created_at"] = pd.to_datetime(df["created_at"]).dt.tz_localize(None). - Any remaining dict/list values in cells must be serialized:
df[col] = df[col].apply(lambda x: json.dumps(x) if isinstance(x, (dict, list)) else x). - Sheet names are capped at 31 characters and can’t contain
[]:*?/\.
Step 5: Preserve Parent and Child IDs
This is where most conversions quietly destroy your data’s usefulness. Before flattening, guarantee every level has a stable identifier:
Rules I follow on every pipeline:
- Never rely on row position alone — exploded rows get reordered by sorts and filters in Excel.
- Carry the parent key into every child row via
meta(as in Step 2). - Synthesize child IDs (
order_id + line_index) when the source doesn’t provide them. A composite key likeORD-1001_0survives any downstream join.
If you’re exporting for someone who’ll analyze in Excel, add these keys explicitly — a VLOOKUP between the orders and line_items sheets on order_id takes them ten seconds and answers 90% of their questions.
Common KeyError Problems (and Fixes)
The failures you’ll actually hit:
KeyErrorinmeta: a nested parent field like["customer", "email"]is missing in some records. Fix: adderrors="ignore", or pre-fill missing keys withrecord.setdefault("customer", {}).record_pathKeyError: some records lack the array entirely. Fix: filter first —[r for r in data if r.get("line_items")].TypeError: unhashable typein DataFrame construction: a list snuck into a flattened field. Fix: your recursive flattener’sisinstance(v, list)branch is being bypassed — check for tuples or nested lists-of-lists.- Silent column loss:
json_normalizeon records with wildly different keys produces a sparse frame, which is correct behavior — but verify withdf.notna().sum()before assuming columns disappeared.
Complete Python Script
Change array_key and parent_keys to match your file — everything else is reusable.
FAQ
Can I convert JSON to Excel without Python? Excel’s Power Query (Data → Get Data → From File → From JSON) handles moderately nested JSON and lets you expand columns via the UI. It works for simple cases, but arrays-of-objects still require manual expansion steps, and refreshable queries on large files get slow.
What’s the difference between json_normalize and flatten_json? json_normalize is a built-in pandas function optimized for uniform records with a known array path. Recursive flattening is custom code that handles irregular, deeply nested structures where every record may have different keys. Use json_normalize first; fall back to recursion when it breaks.
Why does my Excel file show lists or dicts inside cells? Some column still contains Python objects. Serialize them with json.dumps() before calling to_excel(), or explode them with record_path if they’re arrays you want as rows.
How do I handle JSON Lines (.jsonl) files? Replace json.load(f) with data = [json.loads(line) for line in f]. Everything else in the pipeline is identical.
Is there a file size limit when exporting JSON to Excel? Excel caps worksheets at 1,048,576 rows and 16,384 columns. Exploding large arrays can blow past the row limit fast — if you’re near it, export to CSV or split across sheets.
Wrapping Up
Converting nested JSON to Excel without losing data comes down to three decisions: flatten objects with dot notation, explode arrays into rows with parent IDs attached, and export to multiple sheets that mirror your data’s real structure. The script above handles all three — point it at your feed, adjust array_key and parent_keys, and you’ll get a workbook that actually preserves everything. If you’re building recurring exports from platforms like Shopify or Amazon SP-API, wrap this in a scheduled job and you’ll never hand-clean a spreadsheet again.









