What dot-notation flattening produces
Flattening walks every key of a nested object and replaces each intermediate object with a segment of a dotted path. The output is one flat map of path → leaf value, with no remaining nesting. A three-level object collapses to keys like a.b.c.
{
"user": {
"name": "Alice",
"address": { "city": "Paris", "country": "FR" }
},
"active": true
}{
"user.name": "Alice",
"user.address.city": "Paris",
"user.address.country": "FR",
"active": true
}How arrays get indexed
Arrays do not have named keys, so each element is addressed by its position. A list of objects like items expands to items.0.name, items.1.name, and a list of primitives expands to tags.0, tags.1. This turns every leaf into a scalar — exactly what a CSV column or key-value store needs.
{
"items": [
{ "name": "Widget", "qty": 2 },
{ "name": "Gadget", "qty": 1 }
],
"tags": ["new", "sale"]
}{
"items.0.name": "Widget",
"items.0.qty": 2,
"items.1.name": "Gadget",
"items.1.qty": 1,
"tags.0": "new",
"tags.1": "sale"
}Unflatten: rebuild nested JSON from dotted keys
Unflatten is the inverse. It splits each key on the delimiter and recreates intermediate objects — and crucially, when a path segment is a number it rebuilds an array rather than an object with keys "0" and "1". So a round-trip restores the original shape.
// flat input
{
"user.name": "Alice",
"items.0.name": "Widget",
"items.1.name": "Gadget"
}
// unflattened output
{
"user": { "name": "Alice" },
"items": [ { "name": "Widget" }, { "name": "Gadget" } ]
}Choosing a delimiter
The dot is the default because it mirrors JavaScript property access, but it is a poor fit when keys feed into systems where dots are illegal. Switch the delimiter for the destination:/ for URL-style paths, __ (double underscore) for environment variables. The one rule: never pick a delimiter that already appears inside a key name, or unflatten can no longer tell where one path segment stops.
// delimiter "__" for env-var generation
{ "database": { "host": "localhost", "port": 5432 } }
// →
{ "database__host": "localhost", "database__port": 5432 }
// then: DATABASE__HOST=localhost DATABASE__PORT=5432Empty objects and empty arrays
These are the round-trip trap. An empty object or array contains no leaf, so a strict flatten emits no key for it and the branch silently vanishes — flatten then unflatten will not return an identical document. If you need empty containers preserved, use a library option that keeps them, or store the original alongside the flat form.
{ "user": { "name": "Alice" }, "meta": {}, "tags": [] }
// strict flatten — empty branches drop out
{ "user.name": "Alice" }
// unflatten can no longer recreate "meta" or "tags"Flatten JSON in code
When you need this in a script instead of the browser tool, three common options:
// JavaScript — pure recursive flatten (arrays indexed)
function flatten(obj, prefix = "", out = {}) {
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? prefix + "." + k : k
if (v !== null && typeof v === "object") flatten(v, key, out)
else out[key] = v
}
return out
}// Node.js — the flat npm package (handles edge cases)
import { flatten, unflatten } from "flat"
flatten({ a: { b: 1 }, tags: ["x"] }) // { "a.b": 1, "tags.0": "x" }
flatten(obj, { safe: true }) // preserve arrays as leaves
flatten(obj, { delimiter: "__" }) // custom delimiter# Python — pandas json_normalize for analysis pipelines
import pandas as pd
flat = pd.json_normalize(data, sep=".").to_dict(orient="records")[0]
# or expand arrays of objects into rows:
pd.json_normalize(orders, record_path=["items"], meta=["id"], sep=".")Where flattening is used — and next steps
- CSV / spreadsheet export — scalar cells require a flat row.
- Relational columns and key-value stores that reject nested types.
- Config-to-environment-variable generation with a
__delimiter. - Key-level diffing of two JSON documents.
For full implementations — the flat npm package, Python flatten_dict, pandas json_normalize(), circular-reference detection, and more array strategies — read the Flatten Nested JSON guide. If your end goal is a spreadsheet, flatten first then use JSON to CSV.