JSONPath Explained: Querying JSON Like jq, but in the Browser
Every API debugging session lands at the same shell line: curl ... | jq '.items[] | select(.status=="active")' . That works when you have a terminal and the response fits in memory. It breaks down on the iPad during a flight, in a browser DevTools console, on a teammate's machine without jq installed, or when the response is 200MB and shells start choking on pipes. JSONPath does the same query, but it travels with the JSON instead of needing a binary.
I kept reaching for jq until I realized the same filter worked in the browser console with a 30-line JSONPath polyfill. Once I started writing the same query both ways, jq stopped being a dependency and started being a personal preference. JSONPath shipped to prod; jq stayed on my laptop.
JSONPath in one paragraph
JSONPath is the JSON sibling of XPath. A query is a string that names a path into a JSON document, with operators for descending into nested structures, filtering, and slicing. The result is always a list of matched values (or paths to them, depending on the implementation). Stefan Goessner described the original syntax in 2007; in 2024 it finally got an IETF standard as RFC 9535.
The basic shape:
// $ — 루트 객체
$
// $.field — 객체 필드
$.users
// $.field[0] — 배열 인덱스
$.users[0]
// $.field[*] — 배열 전체
$.users[*]
// $..field — 모든 깊이에서 재귀 탐색
$..email
// $.field[?expression] — 필터
$.users[?@.active == true]Six operators cover roughly 90% of every JSONPath query I have ever written. The remaining 10% is filter logic, which is where the implementations diverge.
The five operators that carry the weight
1. Dot notation for fields
// 입력
{ "user": { "name": "Alice", "email": "a@example.com" } }
$.user.name // → "Alice"
$.user.email // → "a@example.com"Identical to plain JavaScript property access. Bracket notation $['user'] is equivalent and required when the key contains a special character or a space.
2. Wildcards and indices
// 입력
{ "users": [
{ "name": "Alice" },
{ "name": "Bob" },
{ "name": "Carol" }
]}
$.users[0] // → { "name": "Alice" }
$.users[-1] // → { "name": "Carol" } (음수 인덱스, RFC 9535)
$.users[*] // → 세 개 모두
$.users[*].name // → ["Alice", "Bob", "Carol"]
$.users[0:2] // → 슬라이스 [start:end), 처음 두 개Negative indices and slicing are the operations that escape Goessner's original draft and only became portable with RFC 9535. Older libraries may not support them.
3. Recursive descent
// 입력
{
"data": {
"user": { "email": "a@example.com" },
"company": {
"owner": { "email": "owner@example.com" }
}
}
}
$..email
// → ["a@example.com", "owner@example.com"]
// 깊이 무관하게 "email" 키를 모두 찾음Recursive descent (..) is the operator that beats jq in expressiveness. The jq equivalent is .. | objects | .email? // empty, which is harder to type and easier to get wrong. Use .. when you do not know how deep the key lives — debugging unknown API shapes, extracting from log dumps, sweeping for secrets.
4. Filters
// 입력
[
{ "name": "Alice", "age": 30, "active": true },
{ "name": "Bob", "age": 25, "active": false },
{ "name": "Carol", "age": 35, "active": true }
]
$[?@.active == true]
// → Alice, Carol
$[?@.age > 28]
// → Alice, Carol
$[?@.active && @.age >= 30]
// → Alice, Carol
$[?match(@.name, "^A")]
// → Alice (RFC 9535 함수)The @ symbol refers to the current item in the iteration. Operators: ==, !=, <, >, <=, >=, &&, ||, !. RFC 9535 also defines four functions: length(), count(), match(), and search().
5. Union (multiple paths in one query)
// 두 필드를 한 번에 추출
$.user['name', 'email']
// → ["Alice", "a@example.com"]
// 두 인덱스 동시 추출
$.users[0, 2].name
// → ["Alice", "Carol"]Less common but useful when extracting a fixed shape from a heterogeneous response.
JSONPath vs jq — what each is better at
The honest answer: jq is a programming language with strings as functions; JSONPath is a query notation. The cases where one beats the other:
JSONPath wins at
- Recursive search.
$..emailbeats the jq equivalent in every measurable way. - Embedding in non-shell environments. Browser, mobile app, K8s manifest, OpenAPI document — JSONPath is just a string. jq needs a binary.
- Tooling that asks for paths. Kubernetes (
kubectl ... -o jsonpath), JSON Schema validation errors, Postman tests — all expect JSONPath, not jq.
jq wins at
- Transformation. JSONPath extracts. jq extracts and reshapes (
{ name, count: (.items | length) }). - Streaming large inputs. jq has a streaming mode for files that do not fit in memory. Most JSONPath implementations load the whole document.
- Shell scripting. Long pipelines are easier in jq because it composes like a Unix tool.
For ad-hoc API debugging in the browser or paste-and-test workflows, JSONPath is faster because there is nothing to install. The JSONPath tester runs the query against pasted JSON and shows results live as you type.
RFC 9535 and the implementation graveyard
JSONPath had no official spec from 2007 until 2024. Every library implemented its own interpretation of Goessner's draft, and those interpretations diverged in subtle ways. RFC 9535 closed the gap. The places where older libraries still differ:
- Result type — some libraries return values, some return paths, some return both. RFC 9535 standardizes on a list of node-list entries (value + path).
- Filter syntax — older Java and PHP libraries use
?(@.x == 1)(parentheses required). RFC 9535 drops the parentheses but most implementations accept both. - Single match vs list match— Goessner's original returned scalar results when only one value matched. RFC 9535 always returns a list.
- Functions — only
length,count,match,searchare standard. Libraries that supportedmin,max,sumare non-standard.
The practical rule: write queries that work in the simplest RFC 9535 subset, and they will run in every JSONPath library shipped after 2024.
Real debugging examples
Find every active user in a paginated response
// 입력: {"page": 1, "items": [{ "id": 1, "active": true }, ...]}
$.items[?@.active].id
// → 활성 사용자의 id만 추출Pull every error message out of a nested response
// 입력: GraphQL 에러 응답 형식
$..message
// → 모든 깊이의 message 필드 — errors[].message, errors[].extensions.message 등
// 모두 한 번에 추출Validate a webhook signature shape
// Stripe webhook의 이벤트 타입과 객체 id 추출
$.data.object.id
$.type
// 두 값이 모두 존재하면 페이로드는 well-formed
// 둘 중 하나라도 비면 rejectExtract via kubectl
# 모든 pod의 상태 phase만 보기
kubectl get pods -o jsonpath='{.items[*].status.phase}'
# 특정 노드의 모든 pod 이름
kubectl get pods --all-namespaces \
-o jsonpath='{.items[?(@.spec.nodeName=="node-1")].metadata.name}'
# 주의: kubectl의 JSONPath는 RFC 9535보다 오래된 변형 — 필터에 괄호 필요Kubernetes uses its own JSONPath variant (Go template-style) that predates RFC 9535. The syntax is close enough that the operators feel familiar, but filter expressions need parentheses. This is the one place where shipping "just the standard" will not work — read the kubectl docs alongside RFC 9535.
Edge cases that bite
- Dotted keys — if the JSON has
"user.email": "a@example.com"as a single key with a dot in it, dot notation cannot reach it. Use bracket notation:$['user.email']. - Missing fields silently return empty — JSONPath does not throw on a missing path. The result is just an empty list. Always check the length before assuming success.
- Empty results vs single-item results — RFC 9535 always returns a list. Older libraries returned a scalar when there was exactly one match. Code that unwraps a single value will break under the new spec.
- Filter type coercion —
@.count == "5"does not match ifcountis the number 5. Filters compare strictly by type, unlike JavaScript's==. - Deep recursion on large documents —
$..walks every node. On a 100MB JSON document this is slow. Prefer a specific path when possible.
Wrapping up
JSONPath is the thing to reach for when the JSON is in front of you, you need a value out of it, and you do not want to leave the browser. The six basic operators cover almost everything; the four standard functions and RFC 9535 fill the rest. jq is still the better tool for transformations and large-file streaming, but for extraction and debugging, JSONPath wins on portability alone.
For testing expressions against a real API response, the JSONPath tester evaluates the query live and shows what each operator matched — useful for the moments when the query "should" work and is silently returning nothing.
Related Tools
JSONPath Tester
Evaluate JSONPath expressions against JSON data with support for root, child, array, recursive, and wildcard operators.
JSON Formatter
Format, minify, and validate your JSON data for better readability.
JSON Schema Validator
Validate JSON data against JSON Schema Draft-07 with detailed error reporting.
JSON Flattener
Convert nested JSON objects into a flat structure with dot notation for nested keys.
Related Articles
How to Generate Secure Passwords in 2026: A Complete Guide
Credential attacks now lean on GPU clusters and ML pattern guessing. What entropy, length, and randomness actually buy you, plus the password manager picks that hold up in 2026.
2025-12-15 · 8 min readData FormatsJSON vs YAML: When to Use What — A Developer's Guide
JSON wins on APIs; YAML wins on configs. Side-by-side syntax, parser behaviour, and where each fits across Kubernetes manifests, REST payloads, and GitHub Actions.
2025-12-28 · 10 min read