To extract all keys from a JSON object, you have to walk it recursively: a top-level key list misses everything nested inside objects and arrays, which is usually the part you need. This guide covers six ways to do it - a no-code online tool, recursive functions in JavaScript, Python, and Ruby, and the command-line tool jq - and then puts all of them through the same awkward document. That test found a bug in this guide's own JavaScript, a jq command that silently returns nothing, and one edge case that fools every method but one.

Why You Need to Extract JSON Keys

Before writing code to process a JSON response, you need a map of what fields are available. Key extraction tells you the full structure of a JSON document, including deeply nested fields that you might miss by reading the raw JSON. It is especially valuable when:

  • Integrating a new third-party API for the first time and wanting to understand its full response shape
  • Building field mappings for data transformations, ETL pipelines, or database imports
  • Documenting a JSON schema for your team so everyone knows what fields exist
  • Debugging why a parser cannot find a field - extracting keys confirms whether the field actually exists in the response
  • Comparing two API responses to find what changed between versions
  • Generating code (e.g. TypeScript interfaces or Python dataclasses) based on a JSON structure

Method 1: JSON Keyper - Instant, No Code

For quick one-off exploration, the fastest way to extract all JSON keys is to paste the document into JSON Keyper and click Extract Keys - no code, no terminal, no dependencies:

  1. Copy your JSON from your API client (Postman, Insomnia, browser DevTools, etc.)
  2. Paste it into the left input box on JSON Keyper
  3. Click Extract Keys
  4. Every key path appears in the right box - user.address.city, items[0].name, etc.
  5. Click Copy to copy all paths to your clipboard

This handles nested objects and arrays automatically, outputting paths in dot notation with bracket notation for array indices. If the JSON lives behind an API rather than on your clipboard, you can also run a curl command online and extract the keys from the live response in the same page.

Two options are worth knowing about, because they turn the raw path dump into something more directly usable. Collapse array indices merges items[0].sku and items[1].sku into a single items[].sku, which is what you want when documenting a schema rather than inspecting one record. And the format selector switches the same extracted structure into value-annotated paths, JSONPath expressions, an indented tree, or a generated TypeScript interface - saving you from writing the code in the sections below at all, for the common cases. Each also has its own page with worked examples: flatten nested JSON to dot-notation paths, generate JSONPath expressions, or generate TypeScript from JSON.

Method 2: JavaScript - Object.keys()

Object.keys() returns only the top-level keys of an object. It is useful for flat JSON but misses nested fields:

const data = {
  "name": "Alice",
  "age": 30,
  "address": { "city": "London", "zip": "EC1A 1BB" },
  "tags": ["developer", "admin"]
};

console.log(Object.keys(data));
// ["name", "age", "address", "tags"]
// NOTE: does NOT include "address.city" or "address.zip"

Method 3: Recursive JavaScript

To extract every nested key path, including keys inside arrays of objects, you need a recursive function:

function getAllKeys(value, prefix = '') {
    const keys = [];
    if (Array.isArray(value)) {
        value.forEach((item, index) => {
            keys.push(...getAllKeys(item, `${prefix}[${index}]`));
        });
    } else if (value !== null && typeof value === 'object') {
        for (const key of Object.keys(value)) {
            const path = prefix ? `${prefix}.${key}` : key;
            keys.push(path);
            keys.push(...getAllKeys(value[key], path));
        }
    }
    return keys;
}

const keys = getAllKeys(JSON.parse(jsonString));
console.log(keys.join('\n'));

For the example above, this outputs:

name
age
address
address.city
address.zip
tags

Arrays are walked rather than listed, so tags appears once instead of as tags[0] and tags[1], while keys inside arrays of objects come out as items[0].sku. On the test document below it produces exactly the same 13 paths as JSON Keyper, in the same order.

Correction, October 1, 2026: the function previously shown here used for...in and handled arrays only one level deep, so an array of arrays came out as matrix[0].0 instead of matrix[0][0]. The version above replaces it.

Method 4: Python

Python's approach is clean and readable using a recursive function with generator-style logic:

import json

def get_all_keys(obj, prefix=''):
    keys = []
    if isinstance(obj, dict):
        for k, v in obj.items():
            full_key = f"{prefix}.{k}" if prefix else k
            keys.append(full_key)
            keys.extend(get_all_keys(v, full_key))
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            indexed_key = f"{prefix}[{i}]"
            keys.append(indexed_key)
            keys.extend(get_all_keys(v, indexed_key))
    return keys

# From a string
data = json.loads(json_string)
print('\n'.join(get_all_keys(data)))

# From a file
with open('response.json') as f:
    data = json.load(f)
print('\n'.join(get_all_keys(data)))

Method 5: Ruby

require 'json'

def get_all_keys(obj, prefix = '')
  keys = []
  case obj
  when Hash
    obj.each do |k, v|
      full_key = prefix.empty? ? k.to_s : "#{prefix}.#{k}"
      keys << full_key
      keys.concat(get_all_keys(v, full_key))
    end
  when Array
    obj.each_with_index do |v, i|
      indexed_key = "#{prefix}[#{i}]"
      keys << indexed_key
      keys.concat(get_all_keys(v, indexed_key))
    end
  end
  keys
end

data = JSON.parse(File.read('response.json'))
puts get_all_keys(data).join("\n")

Method 6: jq - Command Line

jq is a command-line JSON processor available on Linux, macOS, and Windows. These commands were run with jq 1.7:

# Top-level keys, sorted alphabetically (keys_unsorted keeps document order)
jq 'keys' data.json

# Every key path, in the same style as the functions above
jq -r 'paths | select(last | type == "string")
       | map(if type == "number" then "[\(.)]" else "." + . end)
       | join("") | ltrimstr(".")' data.json

# Every distinct key name, at any depth
jq -c '[paths | last | strings] | unique' data.json

# One nested value by path
jq '.user.address.city' data.json

Two commands that circulate widely do not do what they appear to. jq '[paths | select(type == "string")]' - often given as "all string keys" - always returns [], because paths produces arrays, never strings. And jq 'paths(scalars)' skips every key whose value is null or false, along with empty objects and arrays: it is built on select, which treats both of those values as false, so the paths are dropped without a warning.

jq is the right choice for scripts and CI pipelines. It is not, however, a way around memory limits on large files: plain jq parses the whole document before your filter runs. The large JSON files guide measures this - about nine times the file size in memory - and shows how --stream avoids it.

Tested: what each method does with awkward JSON

Every method above handles a tidy nested object. To see where they differ, I ran each of them, exactly as printed, on one small document built from the cases that cause trouble in real data:

{"user.name": "a key with a dot", "user": {"name": "Ada", "tags": ["a", "b"], "address": null},
 "items": [{"sku": "A"}, {"sku": "B", "qty": 2}], "empty": {}, "list": [],
 "matrix": [[1, 2], [3]], "0": "numeric-looking key"}
MethodPathsArray of arraysnull and empty valuesKey order
JSON Keyper13matrix onlyKept"0" moved first
Recursive JavaScript (above)13matrix onlyKept"0" moved first
Recursive JavaScript (previous version)18matrix[0].0 - wrongKept"0" moved first
Python, Ruby22matrix[0][0]KeptDocument order
jq paths command (above)13matrix onlyKeptDocument order
jq paths(scalars)11matrix.0.0DroppedDocument order

Three things stand out:

  • Every method prints user.name twice - once for the top-level key literally called "user.name", once for name inside user - and no dot-notation output can tell them apart. Only JSONPath can: JSON Keyper's JSONPath output writes them as $['user.name'] and $.user.name. If your data's keys might contain dots, use that format.
  • JavaScript reorders keys. Any key that looks like a non-negative integer - "0" here - is listed before all the others, because that is how JavaScript orders object properties. Python, Ruby, and jq keep document order. This rarely matters for a key list, but it explains output that looks shuffled.
  • Python and Ruby list every array position - user.tags[0], user.tags[1], matrix[1][0] - which is why they produce 22 paths. That is complete, but noisy for a document with long arrays; collapsing indices, as JSON Keyper's Collapse array indices option does, gives one line per distinct field instead.

Handling edge cases

  • Keys with dots - dot-notation paths are ambiguous; use JSONPath, which quotes such keys.
  • null values - the key exists and should be listed. Watch for tools built on paths(scalars), which drop it - and drop keys whose value is false, too.
  • Empty objects and arrays - {} and [] contain no keys, but the key that holds them still belongs in the list; otherwise a field that is merely empty in your sample disappears from your field mapping.
  • Numeric-looking keys - "0" is a valid object key, not an array index. JavaScript lists such keys first.
  • Very deep documents - recursive functions use the call stack, so pathological nesting can overflow it; an explicit stack avoids that.

Which Method Should You Use?

ScenarioBest Method
Quick one-off explorationJSON Keyper
JSON from a live APIRun the curl command online, then extract
Keys that may contain dotsJSONPath output
Shell scripts or CI pipelinesjq
Python data pipelinePython recursive function
Node.js / browser applicationJavaScript recursive function
Top-level keys only, flat JSONObject.keys()
Ruby applicationRuby recursive function

Extract All JSON Keys - No Code Required

Paste any JSON and get every nested key path in one click, with types, JSONPath, or a TypeScript type. Free, in your browser, no login.

Open the JSON Key Extractor