What is JSON Flattening?
JSON flattening converts a nested object into a flat key-value map where each key is a delimited path to the original value. Unflattening is the reverse — taking a flat map and reconstructing the nested structure.
Nested JSON:
{
"user": {
"name": "Ravi",
"address": {
"city": "Surat",
"country": "IN"
},
"tags": ["admin", "developer"]
},
"active": true
}Flattened (dot separator):
{
"user.name": "Ravi",
"user.address.city": "Surat",
"user.address.country": "IN",
"user.tags.0": "admin",
"user.tags.1": "developer",
"active": true
}Arrays are indexed numerically: tags.0, tags.1, etc. The flat form and the nested form represent exactly the same data — just reorganized.
Why Flatten JSON?
Elasticsearch / OpenSearch: Nested JSON can cause "mapping explosion" — too many unique field names consuming memory. Pre-flattening to a controlled set of dot-notation keys keeps the index predictable and avoids dynamic mapping issues.
Logging pipelines (Datadog, Splunk, Loki, CloudWatch): Flat log events are easier to query, filter, and aggregate. Most logging agents parse flat key-value pairs better than deeply nested structures.
CSV export: Flat keys map directly to CSV column names. The JSONKit JSON to CSV converter uses flattening internally to handle nested objects.
MongoDB $set operator: MongoDB's $set uses dot notation to update specific fields without replacing the whole document:
db.users.updateOne({ _id: id }, {
$set: { "address.city": "Ahmedabad" } // dot notation — same as flattened key
});Environment variables: Flat by nature. Tools like Viper (Go) and python-dotenv map env var names to config keys using . or __ separators — flattening bridges the gap.
Key-value stores (Redis, etcd, Vault): Store flat key → value pairs. Flattening a config object gives you the right shape for bulk writes.
JavaScript — Flatten and Unflatten
// Flatten a nested object
function flatten(obj, prefix = "", sep = ".") {
const result = {};
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? `${prefix}${sep}${k}` : k;
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
Object.assign(result, flatten(v, key, sep));
} else if (Array.isArray(v)) {
v.forEach((item, i) => {
const arrKey = `${key}${sep}${i}`;
if (item !== null && typeof item === "object") {
Object.assign(result, flatten(item, arrKey, sep));
} else {
result[arrKey] = item;
}
});
} else {
result[key] = v;
}
}
return result;
}
// Unflatten a flat object back to nested
function unflatten(flat, sep = ".") {
const result = {};
for (const [key, value] of Object.entries(flat)) {
const parts = key.split(sep);
let current = result;
for (let i = 0; i < parts.length - 1; i++) {
if (!(parts[i] in current)) current[parts[i]] = {};
current = current[parts[i]];
}
current[parts[parts.length - 1]] = value;
}
return result;
}
// Usage
const nested = { user: { name: "Ravi", address: { city: "Surat" } }, active: true };
const flat = flatten(nested);
// { "user.name": "Ravi", "user.address.city": "Surat", "active": true }
const restored = unflatten(flat);
// { user: { name: "Ravi", address: { city: "Surat" } }, active: true }Using the flat npm package (battle-tested library):
// npm install flat
import { flatten, unflatten } from "flat";
const flat = flatten({ a: { b: { c: 1 } } });
// => { "a.b.c": 1 }
const restored = unflatten(flat);
// => { a: { b: { c: 1 } } }
// Custom separator
const underscored = flatten({ a: { b: 1 } }, { delimiter: "__" });
// => { "a__b": 1 }Go Implementation
func flatten(obj map[string]interface{}, prefix string, sep string) map[string]interface{} {
result := make(map[string]interface{})
for k, v := range obj {
key := k
if prefix != "" {
key = prefix + sep + k
}
switch val := v.(type) {
case map[string]interface{}:
for nk, nv := range flatten(val, key, sep) {
result[nk] = nv
}
case []interface{}:
for i, item := range val {
idxKey := fmt.Sprintf("%s%s%d", key, sep, i)
if nested, ok := item.(map[string]interface{}); ok {
for nk, nv := range flatten(nested, idxKey, sep) {
result[nk] = nv
}
} else {
result[idxKey] = item
}
}
default:
result[key] = v
}
}
return result
}
// Usage
nested := map[string]interface{}{
"user": map[string]interface{}{
"name": "Ravi",
"address": map[string]interface{}{"city": "Surat", "country": "IN"},
},
"active": true,
}
flat := flatten(nested, "", ".")
// flat["user.name"] = "Ravi"
// flat["user.address.city"] = "Surat"
// flat["user.address.country"] = "IN"
// flat["active"] = truePython Implementation
def flatten(obj: dict, prefix: str = "", sep: str = ".") -> dict:
result = {}
for k, v in obj.items():
key = f"{prefix}{sep}{k}" if prefix else k
if isinstance(v, dict):
result.update(flatten(v, key, sep))
elif isinstance(v, list):
for i, item in enumerate(v):
arr_key = f"{key}{sep}{i}"
if isinstance(item, dict):
result.update(flatten(item, arr_key, sep))
else:
result[arr_key] = item
else:
result[key] = v
return result
def unflatten(flat: dict, sep: str = ".") -> dict:
result = {}
for key, value in flat.items():
parts = key.split(sep)
d = result
for part in parts[:-1]:
d = d.setdefault(part, {})
d[parts[-1]] = value
return result
# Usage
nested = {"user": {"name": "Ravi", "address": {"city": "Surat"}}, "active": True}
flat = flatten(nested)
print(flat)
# {"user.name": "Ravi", "user.address.city": "Surat", "active": True}
restored = unflatten(flat)
# {"user": {"name": "Ravi", "address": {"city": "Surat"}}, "active": True}Using the flatten-json package:
# pip install flatten-json
from flatten_json import flatten, unflatten_list
flat = flatten(nested_dict) # default separator: _
flat_dot = flatten(nested_dict, ".") # custom separatorSeparator Options
| Separator | Use case | Example |
|---|---|---|
. (dot) | Most JSON tools, Elasticsearch, MongoDB | user.address.city |
__ (double underscore) | Environment variables (AWS SSM, k8s secrets) | USER__ADDRESS__CITY |
/ (slash) | Vault, etcd, some config formats | user/address/city |
[ bracket notation | Lodash _.set, some query languages | user[address][city] |
Use JSONKit's JSON Flatten tool for instant browser-based flattening and unflattening with configurable separators — no library needed.