Skip to content
LocalOnly
JSON Tools

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.

Updated October 18, 20263 min readTool runs in your browser

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 out

Note 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.internal

A 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.

Frequently asked questions

Command Palette

Search for a tool or command