- $ starts at the document root; [0] selects the first array item.
- Use quoted bracket notation when a key contains dots, spaces, or punctuation.
- Wildcards, filters, slices, and recursive descent are outside this tool's current subset.
On this page
- Start with a small JSON document
- Read object keys and array items
- Quote keys that contain punctuation
- Find one field in a nested API response
- Tell no match from unsupported syntax
- What a fuller JSONPath implementation would select
- Check the dialect before you paste an expression
- Inspect a supported path in JSONPathDesk
JSONPath is a query language for selecting values out of JSON data. An expression is one such query, written as a path: it starts at the root of a JSON document and identifies the values you want to inspect. Start with one object key or array index, then add another segment for each level. The examples below separate simple addresses supported by JSONPathDesk from features that require a broader JSONPath implementation.
Start with a small JSON document
A JSON document is a single JSON value: an object, an array, a string, a number, a boolean, or the literal null. Objects hold key–value pairs and arrays hold ordered items, and both nest inside each other — that nesting is the document’s structure, and a JSONPath query walks it one level at a time.
The sample below has two array items and one key containing a dot, enough to show object keys, array indexes, and quoted names:
{
"items": [
{ "id": "001" },
{ "id": "002" }
],
"a.b": { "n": 7 }
}
It has two top-level keys. items is an array holding two objects, and each object has an id. The other key is written as "a.b" — a single key whose name contains a dot. Dots are legal characters in JSON key names, so this is one key, not two nested levels.
An expression reads left to right. $ is the document root, and each following segment steps one level in: $.items[0].id says from the root, take items, take its first element, take id.
Read object keys and array items
Dot notation names an object key, and a bracketed whole number picks an array slot. Expected results for the sample document:
| Expression | Matches | Path | Type | Value |
|---|---|---|---|---|
$.items |
1 | $.items |
array | [2 items] |
$.items[0].id |
1 | $.items[0].id |
string | "001" |
$.items[1].id |
1 | $.items[1].id |
string | "002" |
Indexes count from zero: [0] is the first element and [1] is the second. The array has no third element, so $.items[2] returns zero matches — the expression is fine, the data just is not there. Indexes must be nonnegative whole numbers; this tool accepts neither [-1] nor [01], as the examples below show.
[2 items] is a container summary, not the array’s contents. Containers are reported by shape — an array as its item count, an object as its key count — so a result row stays one line even when the value underneath is large.
The value of id is the three-character string 001, shown with quotes: "001". The quotes are deliberate. They keep a string visibly different from a number written without quotes, such as the 7 in the next section.
Quote keys that contain punctuation
The dotted key in the sample cannot be reached with dot notation, because dot notation splits on the dot:
| Expression | Expected result |
|---|---|
$.a |
zero matches — no key named a exists at the root |
$.a.b |
zero matches — after a finds nothing, there is no level left to read |
$["a.b"] |
one match — the object that holds n |
$["a.b"].n |
one match — the number 7 |
Bracket notation with a quoted name treats a.b as one literal key name. Single quotes work the same way: $['a.b'].n selects the same value. Both quote styles are expression syntax; inside the JSON document itself, keys still follow normal JSON rules and use double quotes.
Quote any name that is not a plain word. In this tool, a dot segment must start with an ASCII letter, _ or $, and continue with ASCII letters, digits, _ or $ — so keys like 2fa or grid-size cannot be written after a dot. Write them in brackets instead: $["grid-size"]. In a double-quoted bracket name, \" represents a quote character inside the key name.
Find one field in a nested API response
An API response body can be a nested JSON document. In this example, the values you need sit inside a top-level object:
{
"ok": true,
"result": {
"order": { "id": "A-1027", "total": 42 }
}
}
The order id sits three levels down, so the JSONPath query names every level it passes through. Expected results:
| Expression | Matches | Type | Value |
|---|---|---|---|
$.result.order.id |
1 | string | "A-1027" |
$.result.order.total |
1 | number | 42 |
$.order.id |
0 | — | — |
The third row is the common mistake: skipping a wrapper level. $.order.id is a valid query, but it finds nothing, because the root has no order key — the document holds result first. The query has to follow the document’s actual structure, one segment per level. Zero matches is the tool reporting that the path found no such address; the fix is completing the path, not changing syntax. The next section separates this from the tool refusing an expression outright.
Tell no match from unsupported syntax
An empty result and an error mean different things: one says the path found nothing to select, the other says the tool cannot run the expression at all.
A missing path is not an error. $.missing is a well-formed expression in this subset; nothing matches it, so the result is an empty match list. The fix is a corrected key name, not different syntax.
A key that is present but set to null is a different case. Take the one-key document {"present": null}:
| Expression | Matches | Type | Value |
|---|---|---|---|
$.present |
1 | null |
null |
$.missing |
0 | — | — |
Selecting an existing null is a match — the value is the JSON literal null, not an absent key. RFC 9535 draws the same line: a selected null value is not a missing value.
An empty result is also not limited to misspelled keys. It can reflect a wrong structural type: on the sample document, $.items.n returns zero matches because $.items is an array, and arrays have no keys for a dot segment to step into. Nothing matched, and the reason is the type of the parent value, not the spelling of n.
Unsupported syntax is refused before anything runs. $.items[*].id is valid JSONPath in the broader standard — a fuller implementation would use it to select both id values — but the wildcard * is outside this tool’s subset, so the tool reports an unsupported-path error instead of returning an empty result. It never guesses.
| Expression | Expected result in this tool |
|---|---|
$.missing |
zero matches — valid expression, key not present |
$.items[2] |
zero matches — valid expression, index past the end |
$.items[*].id |
unsupported-path error — wildcard |
$.items[?(@.id=="002")] |
unsupported-path error — filter |
$.items[-1] |
unsupported-path error — negative index |
$.items[0:1] |
unsupported-path error — slice |
$..id |
unsupported-path error — recursive descent |
When you see the error, the expression needs a fuller JSONPath implementation. When you see zero matches, the expression ran and found nothing at that address — the key is absent, the index is past the end, or the parent value is not the type the path assumes.
What a fuller JSONPath implementation would select
The syntax this tool refuses is not garbage input: it is valid standard JSONPath, and each kind has a concrete expected result on the same sample document. Knowing that result is what makes the refusal table readable — and it is what you would check a fuller implementation against:
| Expression | What RFC 9535 defines it to select | Expected selected nodes on the sample |
|---|---|---|
$.items[*].id |
wildcard: all elements of the items array, then id in each |
two nodes — "001", "002", in document order |
$.items[?(@.id=="002")] |
filter: test each array element (@) and keep those whose id equals the string 002; the match is the element itself |
one node — the object {"id": "002"} |
$.items[-1] |
negative index: counts from the end of the array, so -1 is the last element |
one node — the object {"id": "002"} |
These expressions do not run in JSONPathDesk: the tool refuses all three with an unsupported-path error. The table gives conceptual selected-node values from RFC 9535, not observed output from an engine and not JSONPathDesk’s export envelope.
The RFC calls a query result a nodelist: a list of nodes, each pairing a value with its location. An API may expose values, paths, or another representation. The standard does not require an array wrapper for a single match. It also states that an empty nodelist is a valid query result (section 2.1.2): zero matches is a normal answer, not a failure.
This table is a support contrast, not a complete tutorial. RFC 9535 defines further selectors, including recursive descent, that this guide does not teach.
Check the dialect before you paste an expression
JSONPath is standardized in RFC 9535, which includes the name and index selectors shown above plus wildcards, recursive descent, slices, filters and more. JSONPathDesk implements only the small subset this guide uses: the $ root, .key dot segments, quoted bracket keys, and nonnegative array indexes. Everything else is refused with an unsupported-path error rather than approximated.
Two consequences follow. First, an expression can be perfectly valid standard JSONPath and still fail here — check which dialect a tool speaks before pasting. Second, the tool’s compact path spelling for matches is its internal canonical form, not the RFC’s Normalized Path format.
JSON itself adds one more rule worth knowing: RFC 8259 says object names should be unique, and systems that do accept duplicates interpret them in different ways. This tool refuses any document with duplicate keys — including equal duplicates — so a result is never read out of an ambiguous document.
Inspect a supported path in JSONPathDesk
To try these expressions on your own JSON, JSONPathDesk implements exactly the documented subset described above: paste a document, type an expression, and read the match table. The same limits apply — no wildcards, filters, slices, recursive descent or negative indexes; documents with duplicate keys are refused; sources are capped at 200,000 UTF-16 code units and 64 nesting levels (a per-run input limit, not a byte or device capacity guarantee); and it is not a full RFC 9535 engine, so an expression it refuses may still be valid standard JSONPath.
A run shows three things: an outline of the document, a match table with Path, Type and Value columns, and a count line such as 1 match for $.items[0].id — singular for one match, plural for any other count (0 matches for $.missing). For very large documents the outline is replaced by a short note, while the match table stays complete. Leave the expression box empty and the tool inspects the whole document as a single match at $ — for the sample, type object with the container summary {2 keys}.
Copy and Download hand you that result as a small JSON object; Download saves it as jsonpath-matches.json. The object holds your expression in the tool’s canonical spelling and the match rows — not a copy of your document:
{
"expression": "$.items[0].id",
"matches": [
{
"path": "$.items[0].id",
"type": "string",
"valueText": "\"001\""
}
]
}
Each match carries path, type, and valueText. valueText keeps the JSON spelling of the value: the string in the example is exported as "001", quotes included, so the string stays visibly different from a number. A container match exports its summary instead of its contents — for $.items that is "[2 items]". Editing either input box afterwards marks the result stale and disables the export until you run the query again. The export is a report about your matches, not a reformatted version of your source.