Just Formatter
← Back to blog

json

How to Filter a JSON Array by Attribute Value

2026-08-017 min read
Try it now — JSON Formatter & ValidatorOpen full screen →

Why filtering a JSON array by attribute comes up so often

You paste an API response into a formatter and it is an array of thirty user objects, but you only care about the three where role is admin. Or you are staring at a log export and need every event where status equals failed. This is one of the most common things developers do with JSON, and it happens constantly during API debugging, log analysis, and config auditing — yet most JSON tools only format and validate, leaving you to write a script for anything beyond that.

This guide covers every practical way to filter a JSON array by attribute value: plain JavaScript for scripts and browser consoles, query languages like JSONPath, JMESPath, and jq for CLI and pipeline use, and a point-and-click option for the common case where writing a query is more overhead than the task deserves.

Filtering with plain JavaScript: Array.prototype.filter

For a one-off script or a browser console, Array.prototype.filter is the simplest option — no dependencies, no syntax to learn beyond JavaScript itself. Pass a predicate function and it returns a new array containing only the items that match.

filterByAttribute.js
const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob',   role: 'user'  },
  { id: 3, name: 'Carol', role: 'admin' },
];

// Exact match
const admins = users.filter(u => u.role === 'admin');
// → [{ id: 1, ... }, { id: 3, ... }]

// Case-insensitive substring match ("contains")
const search = 'ali';
const nameContains = users.filter(u =>
  u.name.toLowerCase().includes(search.toLowerCase())
);
// → [{ id: 1, name: 'Alice', ... }]

filter() never mutates the original array — it returns a new one, which is what you want when you are inspecting data rather than transforming it in place. If you need the original array unchanged for a second query, filter() already gives you that for free.

Filtering by a nested attribute

Real API responses are rarely flat. A condition like address.city is common, but obj.address.city throws if address is missing on some items. A small helper that walks a dot-notation path safely handles this without littering your filter predicate with optional chaining.

getAtPath.js
function getAtPath(obj, path) {
  return path.split('.').reduce(
    (cur, key) => (cur && typeof cur === 'object' ? cur[key] : undefined),
    obj
  );
}

const users = [
  { id: 1, name: 'Alice', address: { city: 'Pune' } },
  { id: 2, name: 'Bob' }, // no address — would throw on obj.address.city
];

const inPune = users.filter(u => getAtPath(u, 'address.city') === 'Pune');
// → [{ id: 1, name: 'Alice', address: { city: 'Pune' } }]
// Bob is safely excluded instead of throwing

This is the same technique full JSON-query tools use internally: represent a nested field as a dot-separated string, then resolve it against each object defensively so a missing branch just fails the condition instead of crashing the whole filter.

Combining multiple conditions with AND / OR

Real filtering usually needs more than one condition — role equals admin AND city contains Delhi, for example. Model each condition as a small object, then combine them with every() for AND or some() for OR:

combineConditions.js
function matches(item, condition) {
  const value = getAtPath(item, condition.attribute);
  if (value === undefined || value === null) return false;
  const target = String(value).toLowerCase();
  const needle = String(condition.value).toLowerCase();
  return condition.operator === 'contains'
    ? target.includes(needle)
    : target === needle;
}

function filterAll(items, conditions, mode = 'AND') {
  return items.filter(item =>
    mode === 'AND'
      ? conditions.every(c => matches(item, c))
      : conditions.some(c => matches(item, c))
  );
}

const conditions = [
  { attribute: 'role', operator: 'equals', value: 'admin' },
  { attribute: 'address.city', operator: 'contains', value: 'del' },
];

filterAll(users, conditions, 'AND'); // both must match
filterAll(users, conditions, 'OR');  // either one matches

This structure — an array of { attribute, operator, value } conditions plus a combine mode — is exactly what a reusable JSON filter needs, whether you are building it into a script or a UI: it stays declarative, so adding a third condition is a one-line change instead of a rewritten predicate.

Query languages: JSONPath, JMESPath, and jq

For CLI pipelines or when you need to embed a query in config (an AWS CLI --query flag, for instance), a dedicated query language avoids writing JavaScript at all. The tradeoff is syntax to learn. Here is the same admin filter in each:

  • JSONPath: $.users[?(@.role=="admin")]
  • JMESPath: users[?role=='admin']
  • jq: '.users[] | select(.role=="admin")'
jq-filter.sh
# Filter with jq from a file or a curl response
jq '.users[] | select(.role=="admin")' users.json

# Multiple conditions (AND) in jq
jq '.users[] | select(.role=="admin" and (.address.city | test("Del")))' users.json

JSONPath is the most widely supported (many API tools and IDEs have a JSONPath evaluator built in). JMESPath is what the AWS CLI and Terraform use for --query. jq is the most powerful for pipelines because it can filter, reshape, and format in a single command. Pick based on where the query needs to live — a script, a CLI pipeline, or a config file — not personal preference alone.

Common pitfalls when filtering JSON

A handful of mistakes account for most 'my filter returns nothing' bugs:

  • Case sensitivity — 'Admin' !== 'admin' with strict equality; normalize with toLowerCase() unless you specifically need an exact case match
  • Missing keys — accessing a nested path on an object that lacks it throws or returns undefined; guard with optional chaining or a safe path resolver
  • Comparing numbers as strings — filtering age === '30' fails against a real number 30; compare with Number(value) === 30 or coerce both sides consistently
  • Array-valued fields — a condition like tags === 'json' will never match if tags is an array; use tags.includes('json') instead
  • Deep equality — filter(u => u.address === targetAddress) compares object references, not contents; compare the specific nested field you care about, or use a deep-equal utility

When a filter unexpectedly returns zero results, log the raw value at the path you're filtering on for one item — typeof mismatches (string vs number) and missing keys explain the overwhelming majority of these bugs.

Filtering JSON in the browser without writing any code

For a one-off check during debugging — 'let me just see the admin users in this response' — writing and running a script is often more overhead than the task deserves. Just Formatter's JSON Formatter includes a point-and-click JSON filter: paste your JSON, switch the preview panel to Filter, and it automatically detects every array of objects in the document, including ones nested inside other objects.

Pick an attribute from a dropdown (nested fields like address.city are detected automatically up to three levels deep), choose equals or contains, and type a value. Add more conditions and toggle AND / OR to combine them — no query language, no script, and the rest of the JSON document stays visible around the filtered array so you can see exactly where the matching records sit.

💡

Try it on the JSON Formatter page: paste an array of objects, click Format, then Filter. Everything runs in your browser — nothing is uploaded.