How to Flatten Nested JSON in JavaScript and Python
A recursive function of about ten lines flattens any JSON document into path keys. Here it is for JavaScript and Python, with the choices that decide whether you can rebuild the original.
To flatten nested JSON in code, a recursive function of about ten lines is enough. The JSON Flattener turns a nested document into path keys in the browser, and its page explains when flattening loses information. This guide is for doing the same thing in code: a short recursive function for JavaScript and Python, the choices it makes, and one common use of the result.
Flatten nested JSON in JavaScript
function flatten(value, prefix = "", out = {}) {
if (value !== null && typeof value === "object" && Object.keys(value).length > 0) {
for (const [key, child] of Object.entries(value)) {
const path = Array.isArray(value) ? prefix + "[" + key + "]" : prefix ? prefix + "." + key : key;
flatten(child, path, out);
}
} else {
out[prefix] = value;
}
return out;
}
flatten({ user: { name: "Ada", address: { city: "Oslo" } }, tags: ["a", "b"], meta: {} });
// {
// "user.name": "Ada",
// "user.address.city": "Oslo",
// "tags[0]": "a",
// "tags[1]": "b",
// "meta": {}
// }It walks the document depth first. Object keys are joined with dots, array indexes are written in brackets, and anything that is not a non-empty object or array becomes a leaf value.
Flatten nested JSON in Python
def flatten(value, prefix="", out=None):
out = {} if out is None else out
if isinstance(value, (dict, list)) and value:
items = value.items() if isinstance(value, dict) else enumerate(value)
for key, child in items:
if isinstance(value, list):
path = f"{prefix}[{key}]"
else:
path = f"{prefix}.{key}" if prefix else key
flatten(child, path, out)
else:
out[prefix] = value
return outNote the out=None default. Writing out={} in the signature would share one dictionary between every call, so results from one document would leak into the next.
Choices that decide whether you can rebuild it
- Array style. Brackets (
tags[0]) keep the difference between arrays and objects visible. Plain dots (tags.0) are simpler, and some tools expect them, but a reader can no longer tell{"0": "a"}from["a"]. - Empty containers. The function keeps
meta: {}as a leaf. Skipping empty objects and arrays gives tidier output, but the key is then gone when you unflatten. - Keys that contain the separator.
{"user.name": "A", "user": {"name": "B"}}flattens to a single key, and one value overwrites the other: the result is{"user.name": "B"}. If your keys can contain dots, check for duplicate paths or pick a separator that cannot appear in keys.
If you plan to turn the result back into nested JSON, keep brackets and empty containers. The JSON Unflattener and its guide cover the reverse direction.
Turning the paths into environment variable names
A common reason to flatten config is to set it through environment variables. Shells accept only letters, digits and underscores in variable names, so export app.db.host=x fails with not a valid identifier. Convert each path to an uppercase name with a double-underscore separator:
const toEnvName = (path) =>
"APP__" + path.replace(/\[(\d+)\]/g, ".$1").split(".").join("__").toUpperCase();
for (const [path, value] of Object.entries(flatten(config))) {
console.log(toEnvName(path) + "=" + value);
}
// APP__DB__HOST=db.internal
// APP__DB__PORT=5432
// APP__SERVERS__0__HOST=a.internalA double underscore leaves single underscores free for use inside names, such as MAX_CONNECTIONS. Remember that every environment variable is a string: the program that reads them has to turn 5432 back into a number.
Related tools
Related guides
Turning user.address.city back into nested objects takes about fifteen lines. Here is a version that builds arrays, fails clearly on conflicting keys and ignores __proto__.
Joining values with commas breaks on the first name that contains one. Here is a short, tested JSON to CSV converter for JavaScript and Python, and the details that make Excel open it correctly.
Loading a 121 MB JSON file took over a gigabyte of memory in jq. Streaming the same file took 3 MB. Here is how to explore large JSON without loading it all at once.
A payload can be 19,847 characters, 20,197 bytes and 1,803 bytes on the wire, all at once. Here is which number an API limit, a database or a browser actually checks.