Flatten JSON (and Unflatten)
100% in your browser
Your data is never uploaded. All processing happens locally on your device.
Read privacy policy →Last updated: 2026-09-17
Paste JSON to flatten it into dot-notation key paths (e.g., users.0.name), or switch to unflatten mode to rebuild nested JSON from flat paths. Supports custom delimiters and bracket notation. Everything runs locally.
What this tool does
Flattens nested JSON into a single-level object with dot-notation keys, or unflattens dot-notation keys back into nested JSON. You can choose the delimiter (default: .) and whether array indices use bracket notation (items[0]) or dot notation (items.0). The two modes are inverses: flatten encodes a hierarchy into flat paths, and unflatten decodes those paths back into the nested form, provided the delimiter and index style match across both steps.
Example
Input (nested):
{"user":{"name":"Alice","address":{"city":"NYC","zip":"10001"}},"tags":["admin","user"]}Output (flattened, bracket notation):
{
"user.name": "Alice",
"user.address.city": "NYC",
"user.address.zip": "10001",
"tags[0]": "admin",
"tags[1]": "user"
}Reverse the process: paste the flat object and switch to Unflatten mode to rebuild the nested structure.
Flatten vs unflatten
| Mode | Input | Output |
|---|---|---|
| Flatten | {"a":{"b":1}} | {"a.b":1} |
| Unflatten | {"a.b":1} | {"a":{"b":1}} |
| Flatten (array) | {"items":[{"x":1}]} | {"items[0].x":1} |
| Unflatten (array) | {"items[0].x":1} | {"items":[{"x":1}]} |
Options
| Option | Default | Effect |
|---|---|---|
| Delimiter | . | Separator between path segments |
| Array index style | Bracket | items[0] (bracket) or items.0 (dot) |
When to use flattening
- CSV export: flatten nested objects so each key becomes a column
- Key-value databases: DynamoDB, Redis, and Cassandra store flat key-value pairs
- Data analysis: flatten JSON into tabular form for pandas, R, or SQL
- Configuration diffs: flatten two configs and diff the flat key-value pairs
- Spreadsheet input: produce flat data that pastes cleanly into Excel or Google Sheets
- Log and telemetry pipelines: flat keys are grepable in a log stream, so a single line carries one leaf and you can filter on a path without parsing the whole document
- Audit and indexing: search engines and audit stores that index individual fields consume the flat form directly, turning each path into its own indexed term
Flattening for CSV export
Most CSV libraries expect a flat list of column names and one value per cell. Nested objects break that model because a single field like address maps to several sub-fields. Flatten first, then write the flat object as one CSV row.
Given a list of orders:
[
{"id":"ord_1","customer":{"name":"Alice","email":"alice@example.com"},"total":42.50,"items":2},
{"id":"ord_2","customer":{"name":"Bob","email":"bob@example.com"},"total":13.00,"items":1}
]Flatten each order (delimiter .) and collect every distinct key as a column header:
id,customer.name,customer.email,total,items
ord_1,Alice,alice@example.com,42.50,2
ord_2,Bob,bob@example.com,13.00,1The column names are the flattened keys, so downstream consumers like pandas, R, or a spreadsheet import the file without a second parsing step. If your objects carry inconsistent keys across rows, union the key sets first so every row aligns under the same header order. Arrays of scalars (such as tags) are best handled separately, either by joining them into a single cell or by exploding them into one row per element before flattening.
Flattening for key-value databases
DynamoDB, Redis, and similar stores map a single string key to a single scalar value. They do not traverse nested objects. Flattening produces one row per leaf, which maps directly onto a PutItem or HSET call.
Take this product document:
{"sku":"abc-123","price":{"amount":19.99,"currency":"USD"},"stock":{"warehouse":"us-east","qty":120}}Flattened with a colon delimiter (useful when keys themselves contain dots):
{
"sku": "abc-123",
"price:amount": 19.99,
"price:currency": "USD",
"stock:warehouse": "us-east",
"stock:qty": 120
}In Redis you can pipe this straight into HSET product:abc-123 with each flat key as a hash field. In DynamoDB the flat keys become attribute names on the item. For range queries over a hierarchy, a slash delimiter (stock/qty) pairs well with prefix scans in key-ordered stores such as LevelDB or RocksDB, because every child path sorts immediately after its parent prefix.
Round-tripping: flatten then unflatten
A round trip takes nested JSON, flattens it, then unflattens the flat result. The final object equals the original when you keep the delimiter and array index style identical in both directions. The tool’s unflatten step parses bracket indices and numeric dot segments back into real array positions, so arrays reconstruct at the correct indices rather than collapsing into objects.
{"server":{"region":"us-east","ports":[8080,8443]},"enabled":true}Flatten (bracket notation):
{
"server.region": "us-east",
"server.ports[0]": 8080,
"server.ports[1]": 8443,
"enabled": true
}Unflatten that output and you get the original document back, with server.ports restored as an array of two numbers. Round trips are handy when you edit the flat form by hand, sort keys, or merge two configs at the leaf level, and then want the nested shape again. Keep the delimiter consistent on both sides: if you flatten with /, unflatten with / too, otherwise the parser treats the dots in the keys as literal characters and nests under the wrong segments.
Common mistakes when flattening
- Mixing delimiters across directions: flattening with
:and unflattening with.produces a broken nest because the parser splits on the wrong character. - Forgetting that keys already contain dots: a field named
config.versioncollapses intoconfig.versionand then unflattens intoconfigcontainingversion, silently changing the shape. Switch to a delimiter your keys do not contain. - Treating empty objects as missing: empty
{}and[]are kept as leaf values, so they survive the round trip. Do not assume absence means emptiness when you diff two flat outputs. - Expecting unflatten to accept an array: unflatten rejects arrays, numbers, strings, and null. It only rebuilds from a flat object.
- Bracket style without bracket parsing: if you flatten with bracket notation but unflatten with dot style,
items[0]becomes a single literal key rather than an array index at 0. - Ignoring key ordering after edits: inserting a new leaf into the flat object and unflattening works, but reordering keys can reorder array reconstruction when indices are inferred from position rather than parsed from brackets.
- Assuming scalars at the root flatten: a top-level number, string, or boolean is not an object and cannot be flattened into key paths. Wrap it in an object first.
Choosing a delimiter
The delimiter joins path segments, so pick one that never appears inside your keys. Dot (.) is the default and reads naturally for most JSON, but it clashes with keys that already use dots, such as version strings (v2.1.0) or domain names. Slash (/) suits hierarchical or filesystem-like data and aligns with prefix scans in key-ordered stores. Colon (:) is compact and rare in object keys, which makes it a safe default for key-value database loads. Pipe (|) is bulky but unambiguous when both dots and slashes appear in your data. Whatever you choose, apply the same delimiter in both directions so the round trip stays lossless. If you target a system that already speaks JSONPath, keep dot for object segments and bracket for array indices, because that mirrors the query syntax the downstream reader expects.
Flattening nested arrays
Arrays add a second dimension to the path. The tool offers two styles for the index segment. Bracket notation wraps the index in square brackets (items[0], items[1].name), which keeps the index visually separate from object keys and is the default. Dot notation writes the index as a normal path segment (items.0, items.1.name), which is shorter and convenient when downstream code splits on a single delimiter without special bracket handling.
For a document with an array of objects:
{"orders":[{"id":"ord_1","total":42.5},{"id":"ord_2","total":13.0}]}Bracket output:
{
"orders[0].id": "ord_1",
"orders[0].total": 42.5,
"orders[1].id": "ord_2",
"orders[1].total": 13.0
}Dot output (same data, dot style):
{
"orders.0.id": "ord_1",
"orders.0.total": 42.5,
"orders.1.id": "ord_2",
"orders.1.total": 13.0
}Bracket notation is safer when object keys can be numeric, because the parser distinguishes a bracketed index from a plain key during unflatten. Dot notation relies on a numeric test for each segment, so a key like 0 is treated as an array index even when the original structure used an object. If you control the schema and no object key is ever a bare integer, dot notation is fine. When arrays nest inside arrays, bracket style stacks cleanly (matrix[0][1]), while dot style flattens both indices into the same delimiter stream (matrix.0.1), which still unflattens correctly because each numeric segment is recognized in turn.
Do it in code
JavaScript:
function flatten(obj, prefix = '', result = {}) {
for (const [key, value] of Object.entries(obj)) {
const path = prefix ? `${prefix}.${key}` : key;
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
flatten(value, path, result);
} else if (Array.isArray(value)) {
value.forEach((item, i) => flatten(item, `${path}[${i}]`, result));
} else {
result[path] = value;
}
}
return result;
}Python (flatten-dict):
from flatten_dict import flatten
flat = flatten(data, reducer='dot')
# Unflatten:
from flatten_dict import unflatten
nested = unflatten(flat, splitter='dot')CLI (jq):
# Flatten with pathto
echo '{"a":{"b":1}}' | jq 'path(.. | scalars) as $p | {($p | join(".")): getpath($p)}' | jq -s 'add'Reading flat paths in your code
Once data is flat, you can address any leaf with a string. This removes the need for optional chaining or nested guards when you pull values out. In JavaScript, split the path on the delimiter and reduce:
const get = (obj, path, delimiter = '.') =>
path.split(delimiter).reduce((acc, segment) => acc[segment], obj);Because the tool writes bracket indices as items[0], a pure split on . leaves the bracket attached. Strip brackets first, or parse the path the same way the unflatten step does: scan for [ and ], push the enclosed number as its own segment, and split the remainder on the delimiter. Keeping a single helper for both reading and rebuilding avoids drift between the two operations.
Performance and limits
Flattening runs in a single recursive pass over the document, so its cost grows linearly with the number of leaves. Every leaf becomes one entry in the output object, and every internal node becomes part of a key string, which means deeply nested documents produce long keys and a large flat map. For payloads in the megabyte range this is negligible. For very wide or very deep inputs, watch the total key length, since some downstream systems cap attribute names or hash field lengths.
The operation runs entirely in your browser. Nothing is uploaded, so you can flatten credentials, internal configs, or customer records without them leaving the tab. There is no network call and no server log, which makes the tool suitable for sensitive data that you would not paste into a hosted API.
Migrating a schema with flattening
When a nested field moves or renames between two API versions, diffing the flat forms is often the quickest way to see every affected path. Flatten both versions, sort the keys, and compare line by line. Renames show up as a removed key and an added key; relocated branches show up as the same suffix under a different prefix. Because the flat form has one row per leaf, a standard text diff pinpoints the exact paths that changed without you tracing the nesting by eye.
This is cheaper than writing a custom recursive comparator for a one-off migration check, and the flat output doubles as a checklist for updating queries, indexes, or templates that reference the old paths.
Validating the output
After a round trip, compare the unflattened document against the original to confirm parity. Serialize both to a canonical string with sorted keys and compare the strings, or compute a checksum over the sorted flat key list. A mismatch usually means the delimiter was not held constant across directions, or that a key contained the delimiter character and silently split into extra segments. The tool preserves null, booleans, numbers, and empty containers as leaf values, so those should match exactly; if a null disappears or an empty array turns into an absent key, the input was edited between steps rather than transformed by the tool itself.
Edge cases handled
- Empty objects
{}and arrays[]are preserved as values in the flat output, not expanded. - Null values are preserved as
nullin the flat output. - Custom delimiters work in both flatten and unflatten modes.
- Unflatten mode detects numeric indices in bracket notation and rebuilds arrays.
- Unflatten mode rejects non-object input with a clear error message.
- Key insertion order from the parsed input is preserved in the flat output.