Skip to content
Back to Blog
By JSONConvert Team··7 min read

Parsing Nested JSON in JavaScript

Most real API responses are not flat. You get an object that holds an array of objects, each holding another object, and somewhere five levels down is the one value you actually need. Reaching it without crashing on a missing key is where most JavaScript bugs around JSON come from. This guide walks through the patterns that hold up against messy, inconsistent data.

Start by Seeing the Shape

You cannot reliably access what you cannot see. A minified API response printed to the console is unreadable, and console.log collapses deep objects into [Object] once they pass a certain depth.

Before writing any access code, format the response so the structure is obvious:

Paste a raw response into the JSON viewer to get a collapsible tree, or the formatter for indented text. Seeing that geo is null on the second address before you write code saves you from a runtime error later. That single null is the kind of thing that breaks naive access.

Accessing Nested Values

Once you have parsed the string with JSON.parse, nested values are reached with dot or bracket notation:

Bracket notation is required when a key has spaces, hyphens, or starts with a number:

This works right up until a key in the chain is missing. res.data.user.addresses[1].geo.lat throws TypeError: Cannot read properties of null (reading 'lat') because the work address has geo: null. One bad record takes down the whole function.

Optional Chaining Is the Default Now

Optional chaining (?.) short-circuits to undefined the moment any link in the chain is null or undefined, instead of throwing:

Pair it with the nullish coalescing operator (??) to supply a fallback only when the value is null or undefined, not when it is 0 or an empty string:

The difference between ?? and || matters here. If a valid latitude is 0, || 0 would replace it pointlessly, while ?? 0 keeps it. Reach for ?? whenever 0, false, or "" are legitimate values.

Looping Over Nested Arrays

The common task is pulling one field out of every object in a nested array. Use map for that:

When the array itself might be absent, guard it so you do not call .map on undefined:

Defaulting to an empty array means downstream code always gets an array, never a crash. For deeper extraction, combine flatMap to flatten one level:

Filtering out the null geo first means the flatMap callback never touches a null.

Pulling One Value Out of a Deep Tree

Sometimes you do not know the exact path, or you want every matching value regardless of where it sits. Writing nested loops for that gets ugly fast. JSONPath expresses it in a single line:

The recursive descent operator .. walks every level, which is handy when an API nests the same field in different places. You can prototype these against your real data in the JSONPath evaluator before porting the logic into code, then implement the confirmed path with plain accessors.

Choosing an Approach

SituationUse
Known path, value always presentDot or bracket notation
Known path, links may be missingOptional chaining ?. with ??
One field from an array of objectsmap over a defaulted array
Unknown depth or scattered matchesJSONPath recursive descent
Exploring an unfamiliar responseJSON viewer tree first

A Reusable Safe-Get Helper

If you are stuck supporting an older runtime without optional chaining, or you want a path as a string, a small helper covers it:

The acc == null check uses loose equality on purpose so it catches both null and undefined in one comparison. For anything modern, prefer native optional chaining; it is faster and reads better than passing string paths around.

The Gotcha That Bites Everyone

JSON.parse gives you plain objects and arrays, nothing more. Dates come back as strings, not Date objects, and a number that exceeds Number.MAX_SAFE_INTEGER (such as a 64-bit ID) loses precision silently. If your API returns "id": 9007199254740993, JavaScript reads it as 9007199254740992. When IDs matter, ask the API to send them as strings, and parse those long numeric fields yourself rather than trusting JSON.parse to keep every digit.

Related Tools