Skip to content

fix(json-formatter): find JSONPath quoted names with escapes, brackets or the other quote - #410

Merged
anurag629 merged 1 commit into
codercops:developfrom
Saifjafri212213:fix/jsonpath-quoted-names
Oct 28, 2026
Merged

anurag629 merged 1 commit into
codercops:developfrom
Saifjafri212213:fix/jsonpath-quoted-names

Conversation

@Saifjafri212213

Copy link
Copy Markdown
Contributor

Summary

Fixes JSONPath quoted names that hold escapes, brackets, the other quote, or that look like an operator. All six paths from the issue now find their value, and so does the path buildJsonTreePath writes for any key I tried.

Closes #405

Cause

lib/jsonpath.ts only wraps jsonpath-plus, and the library cuts an expression up with regular expressions (JSONPath.toPathArray). It does not decode escapes, and a name with a quote, a bracket or a comma is split in the wrong place ($["br[ack]et"] becomes the segments br and acket"). Worse, its _trace runs a segment such as *, .., ^, ~, $, 1:2 or ?(...) as an operator, so $["*"] is a wildcard and not the key *. Fixing only the splitting would still leave those wrong.

What changed

  • lib/jsonpath.ts: a bracket that holds exactly one quoted name is read as an RFC 9535 string literal (in double quotes ' needs no escape and \" is a quote, in single quotes the other way round, and \\ \/ \b \f \n \r \t \uXXXX work in both; [ ] . , inside quotes are characters; blanks inside the brackets are allowed).
    • A name the library already read correctly (letters, digits, _, space, -, no escape) is left exactly as written, so nothing that worked changes.
    • Any other name is replaced in the expression by a harmless alias token, and the same alias is added to every object that has that key, as a non-enumerable property on the parsed copy. The library then finds the real key through it, while *, .., ~, Object.keys and filters never see it. The real key goes back into the matches, the pointer (with ~0/~1 escaping) and the displayed path.
    • The alias is built from a private-use character that is not in the JSON text (as itself or as \uXXXX) or in the expression, so it cannot collide with a key or a value.
    • A bracket that is not a single quoted name (a filter, a union, a slice, an index) is copied as written, and so is a literal with an escape RFC 9535 does not define or one that never closes.
  • lib/json-tree-path.ts: buildJsonTreePath wrote a key called $ as $.$, and the second $ is the root, not the key. It is now written as $["$"]. Every other key is written as before.

Notes for review

  • Lone surrogates written as \uD800 are accepted (RFC 9535 says they are invalid), because JSON.stringify writes a key that has one that way and the tree path has to find it.
  • I did not change the paths shown under the matches. For an awkward key the library writes them unescaped ($['it's']), as it already does for $.* on those keys. Happy to change that if you want them re-usable.
  • I did not add support for a union of quoted names ($['a','b']); jsonpath-plus finds nothing for it today, and a test makes sure it is not shortened to its first name.

How I Tested This

  • Wrote the tests first and ran them on the current code: 16 failed, and the rest passed as guards.
  • lib/__tests__/jsonpath.test.ts now covers: the six paths from the issue, the four that already worked, every escape, [ ] . , inside quotes, names that look like operators (* .. ^ ~ $ @ 1:2 ?(@.x) (1)), the path buildJsonTreePath writes for 50 awkward keys (including ~0, ~1, __proto__, control characters, lone surrogates, astral and non-Latin characters) as a root key and as nested keys and array indexes, wildcard, .. and filters after a name, ~ on a name, the result path, large numbers staying exact, blanks inside brackets, an escape in an otherwise plain name, two awkward names in one path, a document that already uses the private-use characters (as itself and as escapes in either case), a filter that mentions the alias, scalar and array roots, an unreadable escape keeping its old result, unions not being shortened, and a wildcard over an object that has a lookup.
  • Compared the new code with develop on 12,000 random expressions (8,764 distinct) over four documents, using wildcards, .., filters, slices, unions, ^, ~ and quoted names: all 1,186 expressions that returned something before give identical matches and paths. The only differences are 111 expressions that now find a quoted awkward key or one with blanks inside the brackets, and 3 that now reach the script selector [(@.length-1)] and hit its existing loc.indexOf is not a function error (the old code gives the same error for $['a b'][(@.length-1)]).
  • Mutated the new code in 36 ways (each class of the plain-name rule, each escape, \u handling, string and escape skipping in bracketEnd, blank handling, the union guard, the alias being enumerable or only on the root, each pointer escape, un-aliasing of values and paths, and each of the three sentinel checks) and checked a test fails for each. Two survived as equivalent mutants: the \u hex check (the slice is always four characters), and de-duplicating aliases, which I removed because it only changed the cost.
  • In the built app, in the JSON Formatter: pasted a document with awkward keys, switched the output to Tree, clicked each key, and pasted the path from the "JSON path" bar into the JSONPath box. All 14 keys (including *, a,b and $) found exactly one match with the right value. The six paths from the issue found their values, and a deep path through it's, a.b and c] found 99. No console errors.
  • tsc is clean, lint is at develop's 11 warnings, the full suite passes (2407 tests) and next build succeeds. The CI check for a BOM or garbled characters finds nothing.

AI assistance

An AI coding assistant helped write the fix and the tests. I checked the library's behaviour in its source, ran the comparison and mutation checks above, and tried the tree-to-JSONPath flow in the app.

…s or the other quote

jsonpath-plus splits an expression with regular expressions, so a quoted name holding a quote, a backslash, a bracket or a comma, or one that reads like an operator (*, .., ^, ~, $, 1:2), found nothing or ran as the operator. Quoted names are now read as RFC 9535 string literals. A name the library already read correctly is left as written. Any other is swapped for an alias that is also added, hidden, to the parsed data, and the real key is put back in the matches, the pointer and the path. buildJsonTreePath now quotes a key called $, which was written as .$ (the root). Closes codercops#405

@anurag629 anurag629 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Saifjafri212213, smart way around the library. Lint (develop's 11 warnings), all 2417 tests, the build and the pages pass on top of develop. All six paths from #405 find their value, a round trip through buildJsonTreePath works for 31 awkward keys (quotes, backslashes, brackets, *, .., ^, ~, $, a,b, 1:2, ?(@), emoji, a private-use character and more), and 22 ordinary expressions (wildcards, .., slices, unions, filters, $.*~, big numbers) give exactly the same results as develop.

@anurag629 anurag629 added the hacktoberfest-accepted PR counts for Hacktoberfest label Oct 28, 2026
@anurag629
anurag629 merged commit 11bed25 into codercops:develop Oct 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hacktoberfest-accepted PR counts for Hacktoberfest

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSONPath: quoted names with escapes, brackets or the other quote find nothing

2 participants