How to Filter Nested JSON Data in JavaScript: A Practical Guide

How to Filter Nested JSON Data in JavaScript: A Practical Guide
Photo by Rashed Paykary / Unsplash

When working with APIs, the data you receive is often not a flat list, but a nested JSON structure. This introduces challenges when trying to filter or search for specific items. A common issue developers face is attempting to filter at the wrong level of the JSON, which results in empty arrays even though the data clearly exists.


Typical Nested Structure

Consider this generalized JSON:

[
  {
    "brand": "alpha",
    "activities": [
      { "id": 1, "title": "Intro Module", "is_completed": true },
      { "id": 2, "title": "Advanced Module", "is_completed": false }
    ]
  },
  {
    "brand": "beta",
    "activities": [
      { "id": 3, "title": "Setup Guide", "is_completed": false },
      { "id": 4, "title": "Final Test", "is_completed": true }
    ]
  }
]

At the top level, you have brands. Each brand contains its own list of activities, and the is_completed property lives at the activity level, not the brand level.


The Common Mistake

Developers often try to filter directly on the top-level array:

data.filter(e => e.brand === 'alpha' && e.is_completed === false);

This will always return an empty array, because the brand objects do not have an is_completed property — only the nested activities do.


Correct Approach: Filter at the Right Level

The correct way is to first find the brand, then filter inside its activities:

// Find the brand "alpha"
const alpha = data.find(e => e.brand === 'alpha');

// Filter its incomplete activities
const incomplete = alpha.activities.filter(e => e.is_completed === false);

console.log("Incomplete Alpha activities:", incomplete);

Output:

[
  { "id": 2, "title": "Advanced Module", "is_completed": false }
]

Flattening for Easier Filtering

If you want to query across all brands at once, flatten the structure:

const allActivities = data.flatMap(e => e.activities);

const incompleteAlpha = allActivities.filter(
  e => e.brand === 'alpha' && e.is_completed === false
);

console.log("Incomplete Alpha activities:", incompleteAlpha);

Using .flatMap(), you don’t need to drill down brand by brand — you can filter across the entire dataset.


Best Practices and Considerations

  1. Performance Considerations
    For very large datasets, repeated find + filter operations can be costly. Preprocess the data into a lookup map if you need frequent queries.

Readability
Encapsulate filtering logic in helper functions:

const isIncompleteAlpha = e => e.brand === 'alpha' && e.is_completed === false;
const incompleteAlpha = allActivities.filter(isIncompleteAlpha);

Missing Properties
Always account for the possibility that some activities lack is_completed:

e.is_completed === false && "title" in e

Data Types
Some APIs return "false" as a string or 0 as a number. Handle multiple possibilities:

e.is_completed === false || e.is_completed === "false" || e.is_completed === 0

Case Sensitivity
Ensure brand values are consistent. If not, normalize them:

e.brand.toLowerCase() === 'alpha'

Finally

The key lesson when dealing with nested JSON is to filter at the correct level of the hierarchy. Filtering directly on parent objects will not work if the target property exists only on child objects.

  • Use .find() to locate a specific parent, then .filter() inside its children.
  • Use .flatMap() when you want to search across all nested children.
  • Always consider case, data types, missing fields, and performance.

By applying these practices, you’ll avoid the frustrating “empty array” problem and make your filtering logic robust and future-proof.

Support Us