How to Filter Nested JSON Data in JavaScript: A Practical Guide
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
- Performance Considerations
For very large datasets, repeatedfind
+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.
Comments ()