JSON Nested Object Too Deep: Stack Overflow and Recursion Limit Fixes
Quick answer
💡JSON.parse itself does not recurse — it is implemented in C++ with an iterative parser — but JavaScript code that walks the parsed object tree recursively will throw RangeError: Maximum call stack size exceeded when nesting depth exceeds roughly 10,000 frames. The fix is to replace recursive traversal with an explicit stack: push child objects onto an array, pop and process them one at a time. Python raises RecursionError from json.loads when nesting depth exceeds 2000 levels as of Python 3.11.
Error symptoms
- ✕
RangeError: Maximum call stack size exceeded in Node.js or browser JavaScript - ✕
RecursionError: maximum recursion depth exceeded in Python json.loads - ✕
Program freezes without error for deeply nested structures before timing out - ✕
Stack overflow error in Java or C# when deserializing deeply nested JSON - ✕
jq produces no output or exits silently on extremely deep JSON structures - ✕
Performance degrades exponentially beyond 50 levels of nesting in recursive code
Common causes
- •GraphQL responses with deeply nested fragment spreads creating multi-level objects
- •XML-to-JSON conversion tools that mirror the original XML hierarchy without flattening
- •Document databases like MongoDB storing linked data as embedded documents instead of references
- •User-submitted JSON that was intentionally crafted with extreme nesting as a denial-of-service payload
- •Recursive data structures accidentally serialized without cycle detection
- •Configuration files built by composing nested override layers programmatically
When it happens
- •Walking a JSON tree with a recursive function that calls itself for each child object
- •Using JSON.stringify on a parsed object that was subsequently modified to add deep nesting
- •Applying a recursive schema validator to a deeply nested GraphQL response
- •Deserializing XML-to-JSON converted data from legacy enterprise systems
- •Processing user-uploaded JSON files in a web service without depth validation
Examples and fixes
A recursive function that visits every node in a JSON tree crashes with RangeError on deeply nested input.
Replacing recursive tree walk with an explicit stack in Node.js
❌ Wrong
function findAllValues(obj, key) {
const results = [];
if (obj === null || typeof obj !== 'object') return results;
if (key in obj) results.push(obj[key]);
for (const child of Object.values(obj)) {
// Crashes at ~10,000 nesting levels
results.push(...findAllValues(child, key));
}
return results;
}✅ Fixed
function findAllValues(root, key) {
const results = [];
const stack = [root];
while (stack.length > 0) {
const obj = stack.pop();
if (obj === null || typeof obj !== 'object') continue;
if (key in obj) results.push(obj[key]);
for (const child of Object.values(obj)) {
stack.push(child);
}
}
return results;
}The recursive version adds a new call frame to the JavaScript call stack for every level of nesting. V8's default call stack accommodates roughly 10,000 to 15,000 frames before throwing RangeError: Maximum call stack size exceeded. The iterative version maintains an explicit array as a stack, pushing child objects onto it and popping them off in a while loop. The JavaScript call stack stays at a constant depth of one frame throughout the traversal, regardless of how deeply the JSON object is nested. This pattern handles arbitrarily deep structures limited only by available heap memory, not call stack depth.
Convert a deeply nested object to a flat map with dot-notation paths to eliminate nesting issues.
Flattening a deeply nested JSON object to dot-notation keys
❌ Wrong
const config = {
app: {
server: {
database: {
pool: {
min: 2,
max: 10
}
}
}
}
};
// Deep access is verbose and breaks on missing levels
const maxPool = config.app.server.database.pool.max;✅ Fixed
const flat = require('flat');
const config = {
app: {
server: {
database: {
pool: { min: 2, max: 10 }
}
}
}
};
const flatConfig = flat(config);
// { 'app.server.database.pool.min': 2,
// 'app.server.database.pool.max': 10 }
const maxPool = flatConfig['app.server.database.pool.max'];The flat npm package converts a nested object into a flat object with dot-notation string keys. Each leaf value gets a key that encodes its full path through the original structure. This eliminates deep nesting from the data structure entirely, making all values accessible at a single level. The flat package also provides an unflatten function to reconstruct the nested structure when needed. For configuration objects, dot-notation keys are particularly useful because they map directly to environment variable naming conventions and are safe to iterate without recursive traversal.
Call stacks, depth limits, and where they differ
A common misconception is that JSON.parse itself causes the stack overflow on deeply nested JSON. In V8's implementation, JSON.parse is written in C++ and uses an iterative state machine rather than recursive function calls. It can parse deeply nested JSON structures without exhausting the JavaScript call stack. The RangeError: Maximum call stack size exceeded error comes from JavaScript code that walks the resulting parsed object tree using recursive functions.
Each recursive function call in JavaScript adds a frame to the call stack. V8's default call stack depth is approximately 10,000 to 15,000 frames, depending on the size of each frame. A function that calls itself once for each level of JSON nesting will exceed this limit when given an object nested 10,000 levels deep. The limit is not configurable through standard Node.js flags — the --stack-size flag exists in V8 but is not exposed through the Node.js CLI in most versions.
Python handles deep nesting differently. The built-in json.loads function in CPython uses a recursive descent parser written in C. Before Python 3.11, there was no explicit depth limit in the C parser, but the Python interpreter's default recursion limit of 1000 would be hit when walking the resulting data structure. Starting with Python 3.11, following CVE-2023-27043, the json module added an explicit maximum nesting depth of 2000 for the C accelerator. Exceeding this depth raises json.decoder.JSONDecodeError rather than RecursionError, providing a cleaner error message.
You can increase Python's recursion limit with sys.setrecursionlimit(10000), but this is not a safe solution for production services that process user-supplied JSON, because a malicious payload with extreme nesting depth could be crafted to exhaust the Python interpreter's stack and crash the process. The correct fix is to validate input depth before parsing or use iterative traversal for all tree-walking code.
For jq, deeply nested JSON causes jq itself to consume large amounts of stack space for its internal parsing. jq does not have configurable depth limits, but in practice it handles several thousand levels of nesting before running into operating system stack limits. For extremely deep structures, jq --stream processes the file without building a full in-memory tree and avoids the depth limitation entirely. The /tools/json-validator tool can help identify the depth of a JSON structure before committing to processing it.
Measuring nesting depth before processing
Before writing any traversal code, measure the actual nesting depth of the JSON data. This prevents surprises when the data grows over time. A simple depth measurement function using an explicit stack is safe to run on data of any nesting level. Push objects onto a stack along with their current depth level, track the maximum depth seen, and return it after processing all nodes. This gives you an exact number to compare against your recursion limit.
From the command line, jq provides a convenient depth measurement. Run jq '[path(..)] | map(length) | max' file.json to compute the maximum path length across all values in the document. This number represents the deepest nesting level. A value of 5 means the deepest leaf is five levels down from the root. A value of 10,000 means recursive traversal code will almost certainly fail. Validate a sample of your JSON in /tools/json-formatter to visually inspect its structure and identify unexpected nesting.
For Python, measure depth before parsing using a streaming approach. Load the raw JSON string and count the maximum depth by scanning for { and [ characters and tracking the current nesting level as you scan. This avoids calling json.loads on data that would exceed Python's depth limit. Count the nesting depth: scan character by character, increment a counter on opening braces and brackets, decrement on closing ones, and track the maximum value reached.
In production services that accept user-uploaded JSON, log the measured nesting depth alongside the file size for every upload. This creates a historical record that shows when data starts trending toward your limits. Set up alerting when measured depth exceeds 80% of your safe threshold. Catching this trend early allows you to switch to iterative traversal code before the limit is exceeded in production.
For GraphQL responses, check the query structure rather than the response data. A GraphQL query with deeply nested fragment spreads will always produce a deeply nested response. The nesting depth of the response mirrors the nesting depth of the query. Enforce a maximum query depth in your GraphQL server using a query depth limiting directive or middleware, which prevents deeply nested responses from being generated in the first place.
Iterative traversal and flattening strategies
The most reliable fix for stack overflow during JSON tree traversal is to replace recursive functions with explicit stack-based iteration. The pattern is straightforward: create an array to serve as the stack, push the root object onto it, then enter a while loop that continues as long as the stack is non-empty. Each iteration pops one node, processes it, and pushes its children onto the stack. The JavaScript call stack depth stays constant at one frame throughout.
For depth-first traversal, use a standard JavaScript array as the stack and push children in the order you want to visit them. For breadth-first traversal, use an array as a queue and shift from the front instead of popping from the back, or use a proper queue implementation to avoid the O(n) cost of Array.shift on large inputs.
The flat npm package provides a ready-made solution for the common use case of flattening a nested configuration or data object. Install with npm install flat. The flat(obj) call returns a new object with dot-notation keys for all leaf values. The unflatten(obj) call reconstructs the nested structure. This is particularly useful for configuration objects where the nesting was introduced for human readability but causes traversal complexity in code. Flat dot-notation keys are also easier to use with environment variable overrides.
For Python, replace recursive tree-walking functions with an explicit deque from the collections module. Import deque, push the root onto it, and pop items from the right in a while loop. This is equivalent to the JavaScript array stack pattern and handles arbitrary nesting depth. For cases where you must increase the recursion limit for legacy code that cannot be easily refactored, use sys.setrecursionlimit(5000) sparingly and only in controlled environments where input depth is validated externally.
For jq when processing deeply nested structures, use jq --stream which emits path-value pairs rather than building an in-memory tree. Filters on --stream output look different from normal jq filters — each token is emitted as [[path...], value] — but they process data at any nesting depth without stack constraints. An alternative is to use jq to flatten the structure first with jq '[leaf_paths as $p | { path: ($p | join(".")), value: getpath($p) }]' which produces a flat array of path-value objects for further processing.
Edge cases with deep nesting in practice
GraphQL responses are a frequent real-world source of deeply nested JSON. A query that spreads fragments across multiple types can produce responses where the same data structure appears at progressively deeper levels. GraphQL servers like Apollo enforce maximum query complexity and depth limits to prevent this, but many self-hosted instances run without these protections. When consuming GraphQL responses, measure the depth of a sample response during development and add assertions that fail if a production response exceeds your safe threshold.
XML-to-JSON conversion tools faithfully mirror the original XML hierarchy, which can have extreme nesting for document-heavy formats like SOAP envelopes or SVG. An XML document with 50 levels of nesting produces a JSON object with 50 levels of nesting. The converted JSON is technically valid, but traversing it with recursive code fails. When consuming XML-converted JSON, consider flattening the structure at the conversion boundary rather than carrying the deep nesting into your application.
JSON Schema validation libraries that use recursive descent to validate deeply nested schemas can also hit stack limits. The schema itself may be shallow, but validating a deeply nested document against a recursive schema definition causes the validator to follow the schema recursively as it descends into the document. Check whether your JSON Schema validator supports a maxDepth option, or validate document depth as a pre-processing step before running schema validation.
Tail-call optimization, which could convert some recursive patterns into iterative ones automatically, is not available for standard recursive functions in strict mode JavaScript. The TC39 specification includes proper tail calls, but V8 removed support for them after an initial implementation due to debugging complexity. Do not rely on tail-call optimization for JSON tree traversal in any production JavaScript code — always implement explicit iteration.
Denial-of-service attacks using deeply nested JSON are a known vulnerability for web services that parse user-supplied JSON. A payload with 100,000 levels of nesting is only a few hundred kilobytes as a string, but it can crash a service that processes it with recursive code. Always enforce a maximum nesting depth limit at the API boundary before attempting to parse or validate user-supplied JSON. Reject payloads that exceed your depth threshold with a 400 Bad Request response.
Mistakes when dealing with deeply nested JSON
Assuming that JSON.parse is the source of the stack overflow is the most common diagnostic mistake. JSON.parse does not use recursive JavaScript function calls, so increasing the stack size does not help with parse failures. The actual error comes from user-written traversal code that processes the parsed result. Always reproduce the error with a minimal deeply nested test case to confirm which function is on the call stack when RangeError is thrown.
Using JSON.stringify as a depth probe fails for a subtle reason. JSON.stringify, like JSON.parse, is implemented iteratively in V8 and does not throw RangeError on deeply nested objects due to call stack exhaustion. It may throw RangeError: Invalid string length if the resulting string would exceed V8's string size limit, but this is a different error. Developers sometimes use JSON.stringify without error and conclude the nesting depth is safe, then encounter the error later when their own traversal code processes the result.
Setting sys.setrecursionlimit to a very high value in Python for a web service that processes user JSON is a security risk. A payload crafted to be exactly at the new limit will still crash the interpreter with a segfault rather than a Python exception, because CPython does not fully protect against C stack exhaustion when the Python recursion limit is set very high. The safe pattern is to validate nesting depth before calling json.loads using a character-scanning pre-check.
Relying on the flat package without understanding how it handles arrays is a common source of bugs. The flat package converts arrays to object-like structures with numeric keys: an array [a, b, c] at key items becomes three keys items.0, items.1, and items.2. When you unflatten the structure, these become an array again. But if your processing code expects a JavaScript array and receives an object with numeric string keys, array methods like map and filter will not work correctly.
Neglecting to validate JSON depth at the API boundary of a public-facing service leaves the service vulnerable to deep nesting denial-of-service. Measure depth with a fast pre-pass before calling JSON.parse, and return a 400 error for payloads exceeding your limit. A character-counting scan that tracks the current depth as it increments on { and [ and decrements on } and ] can check depth in a single linear pass without parsing the JSON at all.
Designing for safe JSON depth in production
Establish an explicit maximum nesting depth for all JSON data in your system and document it. For most applications, 20 to 50 levels of nesting is more than sufficient. Any JSON structure deeper than this is almost certainly an artifact of data modeling choices that should be revisited rather than accommodated. Document the limit in your API contracts, validate it at every ingestion boundary, and enforce it in your JSON Schema definitions using the maxDepth extension if your validator supports it.
Write all JSON traversal code iteratively from the start. When a new JSON processing function is needed, default to the explicit stack pattern rather than recursive descent. This eliminates an entire class of depth-related bugs before they can occur in production. For teams that frequently write recursive tree walkers, add a linting rule or code review checklist item that flags recursive functions operating on JSON or object trees.
For GraphQL APIs you control, configure query depth limits in the server. Apollo Server supports this through the graphql-depth-limit package. Set the maximum query depth to a value that covers all legitimate use cases with margin to spare, and return a descriptive error for queries that exceed it. This prevents deeply nested responses from being generated rather than having to handle them after the fact.
Test your traversal code with deliberately crafted deeply nested fixtures. Generate a test case with a nesting depth equal to 80% of your limit, one at exactly the limit, and one slightly above. Verify that code below the limit processes correctly and code above the limit fails with a clear error rather than a cryptic stack overflow. These tests catch regressions when refactoring changes a recursive function back from an iterative one.
When consuming JSON from third-party APIs or data sources whose structure you cannot control, add a depth validation step to your data ingestion pipeline. Measure the depth of each incoming payload, log it, and apply different processing strategies for different depth ranges. Shallow structures go through the standard recursive pipeline. Structures above a threshold go through the iterative pipeline. Structures above a hard maximum are rejected and logged for investigation. Use /tools/json-validator to check representative samples from each data source during initial integration to establish baseline depth expectations.
Quick fix checklist
- ✓Measure nesting depth with jq '[path(..)] | map(length) | max' before processing
- ✓Replace recursive tree-walking functions with explicit stack-based iteration
- ✓Use the flat npm package to convert deeply nested configs to dot-notation keys
- ✓For Python, validate depth with a character scan before calling json.loads
- ✓Enforce a maximum nesting depth limit at the API boundary with a 400 error
- ✓Configure GraphQL server query depth limits to prevent deeply nested responses
- ✓Test traversal code with fixtures at 80%, 100%, and 110% of your depth limit
- ✓Validate JSON structure with /tools/json-validator before integrating new data sources
Related guides
Frequently asked questions
Does JSON.parse itself throw RangeError on deeply nested JSON?
No. JSON.parse in V8 is implemented in C++ with an iterative state machine, not recursive function calls. It does not exhaust the JavaScript call stack regardless of nesting depth. The RangeError: Maximum call stack size exceeded error comes from JavaScript code that recursively walks the parsed object tree. The parser handles deep nesting without error; the traversal code after parsing is where the stack overflow occurs.
What is the maximum safe nesting depth in JavaScript?
Recursive JavaScript functions can handle approximately 10,000 to 15,000 levels of nesting before V8 throws RangeError: Maximum call stack size exceeded. The exact limit depends on the size of each stack frame. For practical purposes, anything beyond 50 levels of nesting in application data should be redesigned. For user-supplied data processed iteratively, there is no call-stack depth limit — only available heap memory.
What changed in Python's json module in Python 3.11?
Python 3.11 added an explicit nesting depth limit of 2000 to the C accelerator in the json module, following CVE-2023-27043. Attempting to parse JSON nested more than 2000 levels deep raises json.decoder.JSONDecodeError. In earlier Python versions, deeply nested JSON could cause a RecursionError from the interpreter's default recursion limit of 1000, or could exhaust the C stack and crash the interpreter when the limit was manually increased.
How does the flat npm package handle arrays in nested objects?
The flat package converts array elements to object properties with numeric string keys. An array at key items becomes items.0, items.1, items.2, and so on. When you call unflatten on the result, these numeric keys are converted back to an array. The flat package accepts a safe option that disables array conversion if you want arrays to remain intact as values rather than being flattened into indexed keys.
Can I use sys.setrecursionlimit to fix Python RecursionError?
Increasing sys.setrecursionlimit provides temporary relief for moderate nesting but is not safe for production services processing user-supplied JSON. Setting the limit very high can cause CPython to segfault when C stack space is exhausted, bypassing Python's exception handling. The correct fix is to validate nesting depth before parsing and use iterative traversal code. Reserve sys.setrecursionlimit increases for controlled batch jobs with known input depths.
How can I measure the nesting depth of a JSON file without parsing it?
Scan the raw string or file character by character, maintaining a counter that increments on each opening brace or bracket and decrements on each closing one. Track the maximum value reached. This runs in linear time and requires no JSON parser. You can also use jq with the filter [path(..)] | map(length) | max to measure maximum path length after parsing. For files where parsing itself is the risk, use the character scan approach as a pre-check.
Do denial-of-service attacks use deeply nested JSON?
Yes. A JSON string with 100,000 levels of nesting is only a few hundred kilobytes but can crash services using recursive traversal code. This is a known attack vector for web services that accept user-supplied JSON. Always validate nesting depth at the API boundary using a pre-parse character scan, and reject payloads exceeding your limit with a 400 response. Do not attempt to parse or process the payload before this check.
How does jq handle deeply nested JSON compared to JavaScript?
jq processes JSON in C and handles several thousand levels of nesting before hitting operating system stack limits. For extreme nesting depths, jq --stream processes the file without building a full in-memory tree, eliminating the depth limitation entirely. The --stream flag changes the output format to path-value pairs, which requires different filter syntax but removes any practical depth constraint for jq operations.
All tools run in your browser. Your data never leaves your device. Last updated: 2026-05-06.