Sitemap
Adekola Olawale

Adekola Olawale

Full-Stack Dev | Angular, React, Vue, Node.js | Exploring DevOps, AI, Web3 & Blockchain | Building real-world projects & sharing what I learn.

Handling and Debugging Complex Nested Objects in JavaScript

5 min readNov 14, 2025

--

Press enter or click to view image in full size

In JavaScript development, few things cause more confusion than dealing with deeply nested objects.

They’re like Russian dolls, each one hiding another layer inside.

While nesting helps organize structured data, it can quickly become a debugging nightmare if not properly handled.

In this article, you’ll learn how to effectively navigate, manipulate, and debug complex nested objects in JavaScript.

We’ll go step-by-step with explanations, real-world examples, and practical techniques you can use immediately.

Understanding Nested Objects

A nested object is an object that contains other objects as its property values.

Here’s a simple example:

const userProfile = {
name: "Adekola",
contact: {
email: "adekola@example.com",
phone: {
mobile: "+2348012345678",
office: "+2347012345678"
}
},
address: {
city: "Lagos",
region: {
name: "South-West",
postalCode: 100001
}
}
};

Here, userProfile is a top-level object. Inside it, contact and address are also objects. Inside those, phone and region are further nested objects.

Think of it like a tree structure:
- The root (userProfile)
- Branches (contact, address)
- Leaves (mobile, postalCode, etc.)

Why Nested Objects Are Tricky

Nested objects are great for organizing related data, but they come with challenges:

  • Deep access paths are prone to runtime errors (e.g., accessing userProfile.address.region.code when region doesn’t exist).
  • Mutation bugs occur when changing one nested property unexpectedly affects another due to shared references.
  • Debugging becomes harder because console outputs often truncate or oversimplify nested structures.

Let’s dive into how to handle these effectively.

1. Safely Accessing Deeply Nested Properties

The biggest issue developers face is:

“Cannot read properties of undefined (reading ‘x’)”

This happens when you try to access a property that doesn’t exist somewhere along the chain.

❌ Problematic Approach

console.log(userProfile.address.region.code); // ❌ Error: Cannot read properties of undefined

If region doesn’t exist, JavaScript throws an error.

✅ Safe Approach: Optional Chaining

Optional chaining (?.) safely checks if each part exists before accessing the next.

console.log(userProfile?.address?.region?.postalCode); // ✅ 100001
console.log(userProfile?.address?.region?.code); // ✅ undefined, no error

How it works:
The ?. operator stops the evaluation if a part of the chain is undefined or null.

Think of it as JavaScript’s way of saying, “If it exists, keep going; otherwise, return undefined.”

2. Checking for Deeply Nested Keys

Sometimes, you just want to know if a nested property exists before using it.

Using in Operator

if ("region" in userProfile.address) {
console.log("Region exists!");
}

However, in only checks one level deep.

Deep Check Helper Function

You can create a small utility to check nested keys dynamically.

function hasNested(obj, path) {
return path.split('.').reduce((acc, key) => acc && acc[key], obj) !== undefined;
}

console.log(hasNested(userProfile, 'address.region.postalCode')); // ✅ true
console.log(hasNested(userProfile, 'address.country.name')); // ❌ false

Explanation:
This function takes a path string ('address.region.postalCode') and walks through each level safely using reduce.

3. Dynamically Accessing and Updating Nested Values

Imagine receiving dynamic keys from user input or API responses. You can’t hardcode every property name.

Example Scenario

function getValue(obj, path) {
return path.split('.').reduce((acc, key) => acc?.[key], obj);
}

console.log(getValue(userProfile, 'contact.phone.mobile')); // ✅ "+2348012345678"

To update a nested property dynamically:

function setValue(obj, path, value) {
const keys = path.split('.');
let current = obj;

for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}

current[keys[keys.length - 1]] = value;
}

setValue(userProfile, 'address.region.name', 'South-West Nigeria');
console.log(userProfile.address.region.name); // ✅ "South-West Nigeria"

In this code:
We split the key path into parts, iterate through each, and create intermediate objects if they don’t exist.

Get Adekola Olawale’s stories in your inbox

Join Medium for free to get updates from this writer.

This prevents undefined errors during assignment.

4. Debugging Nested Objects Like a Pro

a) Use console.dir() Instead of console.log()

When you console.log() an object, especially in browsers, it might collapse nested structures or show references that don’t update in real time.

console.dir(userProfile, { depth: null });

Why it’s better:
console.dir() prints the full object hierarchy, making it easier to inspect deeply nested values.

b) JSON Stringify for Readable Snapshots

console.log(JSON.stringify(userProfile, null, 2));

This formats the output neatly, letting you visualize nested objects clearly.

c) Object Traversal Debug Helper

To print all nested keys and values, use recursion:

function printNested(obj, indent = 0) {
for (const key in obj) {
const value = obj[key];
console.log(`${' '.repeat(indent)}${key}: ${typeof value === 'object' ? '' : value}`);
if (typeof value === 'object' && value !== null) {
printNested(value, indent + 2);
}
}
}

printNested(userProfile);

This is especially helpful when debugging large, complex JSON responses from APIs.

5. Avoiding Common Pitfalls

❌ Mutating Shared Nested Objects

Objects in JavaScript are references.

Changing one can unexpectedly change another.

const newUser = userProfile;
newUser.address.city = "Abuja";

console.log(userProfile.address.city); // ⚠️ "Abuja" – Both changed!

✅ Deep Cloning Instead

Use structuredClone() or JSON.parse(JSON.stringify()) for deep copies:

const safeUser = structuredClone(userProfile);
safeUser.address.city = "Abuja";

console.log(userProfile.address.city); // ✅ "Lagos"

6. Handling Nested Objects from APIs

Real-world APIs often return deeply nested JSON structures. To handle them efficiently:

  • Validate responses using tools like zod or joi.
  • Flatten objects when needed using utility functions.
  • Map data into simpler models before using in your app.

Example:

const apiResponse = {
data: {
user: {
profile: {
name: "Adekola",
details: { country: "Nigeria", verified: true }
}
}
}
};

const name = apiResponse?.data?.user?.profile?.name || "Unknown";

7. Flattening Nested Objects

Flattening can simplify processing or comparing objects.

function flattenObject(obj, prefix = '', result = {}) {
for (const key in obj) {
const value = obj[key];
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
flattenObject(value, newKey, result);
} else {
result[newKey] = value;
}
}
return result;
}

console.log(flattenObject(userProfile));

Output:

{
"name": "Adekola",
"contact.email": "adekola@example.com",
"contact.phone.mobile": "+2348012345678",
"address.region.name": "South-West",
"address.region.postalCode": 100001
}

Conclusion

Handling and debugging complex nested objects doesn’t have to feel like exploring a dark maze.

By using optional chaining, safe access utilities, debugging techniques, and deep cloning, you can navigate nested structures with confidence and precision.

When working with deeply nested data, remember:

  • Always check existence before accessing.
  • Use utility functions for consistent operations.
  • Avoid direct mutations to prevent hidden bugs.
  • Visualize your objects when debugging.

Master these techniques, and you’ll turn messy nested data into a structured, predictable system that’s easy to debug and maintain.

--

--

Adekola Olawale
Adekola Olawale

Written by Adekola Olawale

Full-Stack Dev | Angular, React, Vue, Node.js | Exploring DevOps, AI, Web3 & Blockchain | Building real-world projects & sharing what I learn.