How to flatten nested json for elasticsearch document indexing
- Step 1Save a representative document as a .json file — The flattener reads a dropped file, not pasted text. Save a single representative Elasticsearch document — one that includes every field your mapping should cover — as a
.jsonfile. Fields absent from the sample will not appear in the flattened output, so pick a document with the fullest field set you index. - Step 2Drop the file onto the dropzone above — Flattening runs entirely in your browser via the in-page logic — no upload. The header shows the file name and size; if the file is invalid JSON the tool surfaces a
SyntaxErrorrather than producing partial output. - Step 3Set the delimiter to a dot — Leave the Delimiter box as
.(the default). Elasticsearch field paths use dots, souser.profile.agein the flat output maps directly to the mapping pathuser.profile.age. The box accepts up to 3 characters if you need a multi-char separator. - Step 4Choose how arrays should be written —
dot.0.keyexpands arrays to indexed keys (tags.0,tags.1);arr[0].keyuses bracket indices (tags[0]); keep arrays leaves every array verbatim. For Elasticsearch multi-value fields you almost always want keep arrays — indexingtags.0/tags.1as separate fields defeats the purpose of a multi-value field. - Step 5Click Flatten JSON and review the key count — The result panel shows
inputKeys → outputKeys. A large jump (e.g. 4 → 380) usually means an array of objects got expanded into per-element keys — switch to keep arrays or model that array with Elasticsearch's nested field type instead of flattening it. - Step 6Use the flat keys to write an explicit mapping — Copy the flattened document, then run it through json-schema-generator to infer types, and translate: string →
keywordortext, number →integer/float, boolean →boolean. Define these explicitly in your index'spropertiesblock instead of relying on dynamic mapping.
Array-handling modes and what Elasticsearch sees
The three array modes from the toolbar, applied to the same input. Only keep arrays preserves a value Elasticsearch can treat as a native multi-value field.
| Mode (toolbar label) | Input | Flattened output keys | Elasticsearch implication |
|---|---|---|---|
| dot.0.key (default) | {"tags":["a","b"]} | tags.0: "a", tags.1: "b" | Two separate fields — defeats multi-value matching; positional and brittle |
| arr[0].key | {"tags":["a","b"]} | tags[0]: "a", tags[1]: "b" | Bracket keys contain [ ] — usually need escaping or renaming before they are valid field names |
| keep arrays | {"tags":["a","b"]} | tags: ["a","b"] | Native multi-value field — the correct choice for tags, categories, primitive arrays |
| dot.0.key | {"items":[{"id":1},{"id":2}]} | items.0.id: 1, items.1.id: 2 | Loses element relationships — use the nested mapping type for arrays of objects instead of flattening |
Options exposed in the UI vs fixed internal behaviour
What you can actually change in the toolbar, and the behaviours that are fixed in the flattening logic and not user-configurable.
| Behaviour | Configurable? | Value / detail |
|---|---|---|
| Delimiter | Yes — text box | Default .; up to 3 characters; empty input falls back to . |
| Array handling | Yes — three buttons | dot.0.key / arr[0].key / keep arrays (default dot.0.key) |
| Max nesting depth | No — fixed | 10 levels; subtrees deeper than 10 are left nested as the value of the depth-10 key (not flattened further) |
| Output indentation | No — fixed | Always pretty-printed with 2-space indent |
| Input method | Fixed — file only | Drop a .json file; there is no paste textarea |
Cookbook
Real before/after documents headed for an Elasticsearch index. The flattener emits one single-level object; arrays and deep subtrees are where the surprises live.
Nested document flattened with dot keys for mapping design
ExampleA typical event document with two levels of nesting. Dot delimiter produces field paths that match what you write in the index mapping's properties block.
Input (event.json):
{
"user": { "id": 42, "address": { "city": "NYC", "zip": "10001" } },
"action": "login"
}
Delimiter: . Array handling: dot.0.key
Output:
{
"user.id": 42,
"user.address.city": "NYC",
"user.address.zip": "10001",
"action": "login"
}
Mapping draft:
"user.address.city": { "type": "keyword" }Primitive array: dot mode vs keep arrays
ExampleA tags array is the classic case where dot mode does the wrong thing for Elasticsearch. dot.0.key splits one multi-value field into N positional fields; keep arrays leaves the array intact.
Input:
{ "tags": ["sale", "new", "featured"] }
dot.0.key →
{
"tags.0": "sale",
"tags.1": "new",
"tags.2": "featured"
} (3 fields — wrong for a multi-value field)
keep arrays →
{
"tags": ["sale", "new", "featured"]
} (1 native multi-value field — correct)Array of objects: why nested mapping beats flattening
ExampleAn order with line items. Flattening expands each element to positional keys, which destroys the per-item relationship Elasticsearch's nested type preserves. Keep this array as a nested mapping instead.
Input:
{ "items": [ {"sku":"A","qty":2}, {"sku":"B","qty":1} ] }
dot.0.key →
{
"items.0.sku": "A",
"items.0.qty": 2,
"items.1.sku": "B",
"items.1.qty": 1
}
Better: keep arrays, then map items as:
"items": { "type": "nested", "properties": { ... } }Empty objects and arrays disappear
ExampleThe flattener emits a key only for leaf values. Empty objects and empty arrays produce no key at all — a frequent surprise when a sometimes-populated field is empty in your sample document.
Input:
{
"meta": {},
"labels": [],
"note": null,
"name": ""
}
Output:
{
"note": null,
"name": ""
}
meta and labels are gone — only null and empty-string leaves survive.
Pick a sample document where those fields are populated.Deep document hits the 10-level ceiling
ExampleThe flattener stops at depth 10. Anything nested below that is left as a JSON subtree under the depth-10 key rather than being flattened further — so your flat key set silently stops short for very deep documents.
Input (12 levels deep):
{ "l0": { "l1": { ... { "l10": { "l11": "leaf" } } ... } } }
Output (stops at level 10):
{
"l0.l1.l2.l3.l4.l5.l6.l7.l8.l9.l10": { "l11": "leaf" }
}
The l11 subtree is left nested — re-flatten that fragment
separately if you need it indexed.Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
Array of objects expands into hundreds of positional keys
By designIn dot or bracket mode, an array of objects becomes items.0.id, items.0.qty, items.1.id … one key per element field. A 200-element array of 5-field objects produces 1,000 keys. This is intentional flattening, but it is wrong for Elasticsearch — model arrays of objects with the nested type and use keep arrays so the array stays a single field.
Primitive arrays split into separate fields in dot mode
By design{"tags":["a","b"]} becomes tags.0 and tags.1, not a multi-value tags field. If you want Elasticsearch's native multi-value behaviour, switch the Array handling button to keep arrays before flattening.
Empty object or empty array produces no key
Expected{} and [] emit nothing — the flattener only records keys for leaf values (string, number, boolean, null). A field that is empty in your sample document simply will not appear in the flat key set, so it will be missing from any mapping you derive. Choose a fully-populated sample.
Document deeper than 10 levels is only partially flattened
ExpectedMax depth is fixed at 10 and is not exposed in the UI. Beyond level 10 the remaining subtree is left nested as the value of the depth-10 key. For unusually deep documents the flat key set stops short — flatten the deep fragment in a second pass if you need those inner fields indexed.
Literal dot in a key collides with a nested path
Data lossIf a document has both a key literally named a.b and a nested path a → b, both flatten to the key a.b and the later one silently overwrites the earlier (last write wins). With dot-heavy field names from upstream systems, pick a delimiter that does not occur in your keys (e.g. ~ or __) in the Delimiter box to avoid the collision.
Bracket-mode keys contain [ and ]
Mapping riskarr[0].key mode produces keys like items[0].sku. Square brackets are awkward as Elasticsearch field names and usually need renaming before they are usable. Prefer dot (then rename) or keep arrays; if you must use bracket keys, run them through json-key-renamer first.
Invalid JSON throws instead of flattening
errorThe flattener calls JSON.parse on the file contents; a trailing comma, single quotes, or a NDJSON file (multiple objects, one per line) throws a SyntaxError and no output is produced. Flatten one well-formed JSON document at a time. For repairs use json-format-fixer; for NDJSON, flatten each line as its own document.
Scalar or string root produces an empty-string key
ExpectedIf the file's top-level value is a bare scalar (42, "hi"), the result is {"": 42} — an empty-string key. Elasticsearch documents must be objects, so wrap scalars in a named object before indexing. The flattener is built for object/array roots.
Free-tier 2 MB file limit
Tier limitJSON tools cap free-tier input at 2 MB and one file at a time. A large bulk export will be rejected before flattening. Flatten a single representative document for mapping design (you rarely need the whole export), or upgrade to Pro (100 MB) for larger files.
Frequently asked questions
Does the flattener keep array values as Elasticsearch multi-value fields?
Only in keep arrays mode. The default dot.0.key mode expands {"tags":["a","b"]} into tags.0 and tags.1 — two separate fields, which is wrong for a multi-value field. Click the keep arrays button before flattening so the array stays a single tags field that Elasticsearch can store natively.
Should I flatten arrays of objects for Elasticsearch?
No. Arrays of objects (e.g. an order with multiple line items) should use Elasticsearch's nested field type, which preserves the relationship between fields inside each element. Flattening them to items.0.sku, items.1.sku loses that relationship and breaks cross-object queries. Use keep arrays mode for those fields and define a nested mapping.
Why is a field missing from the flattened output?
The flattener emits a key only for leaf values. If the field was an empty object {} or empty array [] in your sample document, it produces no key. Pick a sample where every field you care about is populated, then derive the mapping from that flat key set.
Can I change the max nesting depth?
No — depth is fixed at 10 levels and is not exposed in the toolbar. Subtrees deeper than 10 are left nested as the value of the depth-10 key rather than flattened further. For unusually deep documents, flatten the inner fragment in a second pass.
How do I get an index mapping from the flat keys?
Run the flattened document through json-schema-generator to infer types, then translate them to Elasticsearch types: string → keyword or text (choose based on whether you need full-text search), number → integer/float, boolean → boolean. Write these into the index properties block as an explicit mapping rather than relying on dynamic mapping.
Why does my flat document have keys with square brackets?
You used the arr[0].key (bracket) array mode, which writes items[0].sku. Brackets are awkward as Elasticsearch field names. Use the default dot.0.key mode, or keep arrays for multi-value fields, and rename any leftover positional keys with json-key-renamer.
Can I paste a document instead of dropping a file?
No — this tool reads a dropped .json file; there is no paste textarea. Save your document to a file first. If you have a JSON string in your clipboard, paste it into a file in your editor, save it, and drop that file.
What happens to null and empty-string values?
Both are preserved as leaf values: null stays null, "" stays "". This matters for Elasticsearch because a null field is not indexed by default while an empty string is — the flattener keeps the distinction intact so your mapping and null_value settings behave as intended.
Does my log or document data get uploaded?
No. Flattening runs entirely in your browser. Log payloads, API response bodies, and any PII inside the documents are never transmitted to JAD Apps servers. Only an anonymous counter (file processed, no content) is recorded for signed-in dashboard stats, which you can opt out of.
Can I feed NDJSON (one document per line) directly?
No. The flattener parses the whole file as a single JSON value, so an NDJSON file (multiple objects separated by newlines) throws a SyntaxError. Flatten one document at a time, or split the NDJSON and flatten each line as its own document. For the reverse trip, json-unflattener rebuilds nested structure from flat keys.
How big a document can I flatten on the free tier?
Free tier caps JSON input at 2 MB, one file at a time. That is plenty for a representative mapping-design document. Pro raises it to 100 MB with batches of up to 10 files; higher tiers go further. For mapping work you rarely need more than a single sample document anyway.
Can I run flattening in an automated indexing pipeline?
Yes. GET /api/v1/tools/json-flattener returns the option schema (delimiter, arrayHandling); pair the @jadapps/runner and POST your document plus options to /api/v1/tools/json-flattener/run on the local runner. Document content stays on your machine — the runner executes locally. A common pipeline is: capture document → flatten (keep arrays) → derive mapping → bulk-index.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.