What JSON Flattening Does
Flattening collapses a tree of nested objects and arrays into a single-level map where each key is the full path to the original value. Every leaf primitive stays exactly as it was; only the shape changes. The reverse operation — unflattening — walks each key, splits it on the separator, and rebuilds the original tree.
Example: Nested → Flat
// Input
{
"user": {
"id": 42,
"name": "Ada",
"profile": {
"bio": "Mathematician",
"social": {
"twitter": "@ada",
"github": "ada"
}
},
"roles": ["admin", "editor"]
},
"org": "Analytical Engines Inc"
}
// Output (dot separator, bracket array)
{
"user.id": 42,
"user.name": "Ada",
"user.profile.bio": "Mathematician",
"user.profile.social.twitter": "@ada",
"user.profile.social.github": "ada",
"user.roles[0]": "admin",
"user.roles[1]": "editor",
"org": "Analytical Engines Inc"
}Separator Choices
Different ecosystems use different conventions. The flattener supports all four:
- Period (
user.name) — default. JSONPath style. Used by Lodash, Ramda, most JavaScript libraries, and i18n-js. - Slash (
user/name) — file-path style. Used by JSON Pointer (RFC 6901), Kubernetes config patches, and some HTTP APIs. - Underscore (
user_name) — flat environment-variable style. Used by Rails/Hanami i18n, Terraform variable files, and.envflatteners. - Custom — any string. For example,
__is common in formbot and react-intl when keys naturally contain dots.
Array Notation Options
Two common ways to represent array indices in flat keys:
- Bracket notation (default):
roles[0],users[0].name. Easy for humans to read, matches JavaScript and JSONPath. - Index as segment:
roles.0,users.0.name. Simpler to parse (every segment is just a string split by the separator), used by Lodash_.setand_.get.
Pick based on your consumer. The unflattener accepts both and auto-detects.
Common Use Cases
1. CSV Export
CSV cells can only hold scalar values, so a nested JSON API response can't go into CSV directly. Flatten first — each flat key becomes a column header, each leaf value becomes a cell. Multi-row arrays require a decision: either keep each item as separate indexed columns (roles[0], roles[1]) or explode to multiple rows. The flat form makes both strategies possible.
2. i18n Translation Platforms
Services like Crowdin, Lokalise, and POEditor accept translation files but mostly want flat key-value pairs. Your source may use nested JSON for organisation (auth.login.title, auth.login.cta) — flatten to upload, unflatten after download. Round-tripping is lossless if your keys don't contain the separator.
3. Diffing Two Deep JSONs
Line-by-line diff on pretty-printed nested JSON is noisy — adding a leaf changes brace indentation on every ancestor line. Flatten both sides first and the diff becomes one line per changed value, which is what you actually care about.
4. Feature-Flag Platforms
LaunchDarkly, ConfigCat, and most flag services expose values as string-keyed scalars. If your source of truth is a nested JSON config, flatten before pushing. Dot-notation keys match the way those UIs display rules.
5. HTTP Query Strings
?user[name]=Ada&user[roles][0]=admin is bracket-flat JSON in a URL. Flattening a payload with bracket array notation gives you the shape most server-side frameworks (Rack, Express+qs, Rails) parse automatically.
Lossless Round-Trip Rules
Flatten → Unflatten returns identical JSON when:
- No key contains the chosen separator, OR separators inside keys are escaped.
- Arrays contain homogeneous indices (no gaps like
[0],[5]). - The flattener preserves empty objects and empty arrays as literal values.
Edge cases that need care: null values are preserved; empty strings are preserved; booleans and numbers keep their type (no stringification).
Example in Code
In JavaScript, flattening is a 15-line recursion:
function flatten(obj, prefix = "", out = {}) {
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? `${prefix}.${k}` : k;
if (Array.isArray(v)) {
v.forEach((item, i) => {
if (item !== null && typeof item === "object") {
flatten(item, `${key}[${i}]`, out);
} else {
out[`${key}[${i}]`] = item;
}
});
} else if (v !== null && typeof v === "object") {
flatten(v, key, out);
} else {
out[key] = v;
}
}
return out;
}Related Tools
- JSON Formatter — pretty-print and validate first.
- JSON to CSV — the natural next step after flattening.
- JSON Key-Value Extractor — simpler extractor without full flattening.
- JSON Diff — pairs well with flatten for cleaner diffs.