0.3.0

Bug fixes

  • $..[*] / $..[0] / $..[1:3] no longer return duplicates when descending into maps whose values are lists. (collect_all_lists was double-counting list values found inside maps.)
  • Infix ! is now rejected by the parser instead of crashing the evaluator. ! only participates as a prefix operator (!@.x, !(...)); using it as a binary operator used to bypass the parser and crash inside a non-existent binary Kernel.! function.
  • Built-in functions are crash-safe. min()/max() on [], sum()/avg() on non-numeric or empty data, concat() on a map, and length() on unsupported types all return nil (or 0 for sum) instead of raising.
  • Division and modulo by zero in filters no longer crash. They propagate a "Nothing" marker that the filter treats as no match.
  • Missing keys are distinguishable from explicit null inside filters. @.missing == null now returns false; @.missing != null returns true. Two missing paths still compare equal (Nothing == Nothing). Top-level $.missing still returns nil for backward compatibility.
  • Mixed-type ordering comparisons return false instead of leaking BEAM term-order (e.g. "hello" > 10 is no longer truthy in filters).
  • Parser errors include line numbers and the unparsed remainder when the parser only partially consumes input.
  • Parser accepts non-binary input gracefully — returns {:error, _} rather than FunctionClauseError.

Performance

  • Array slicing is now linear-time. A $[::2] slice on a 100,000-element list went from ~9 s to ~3 ms (~3,000× faster). $[::-1] went from ~18 s to ~5 ms.
  • Single-index access avoids tuple allocation, so root-reference filters like $[?(@.value > $[0].value)] are ~25× faster and use ~50× less memory.
  • Enum.reduce + reverse replaces ++ appends in multi-key bracket access and in the comparison-chain grouping helper, removing quadratic behavior on larger inputs.
  • collect_all_lists is no longer recursive into already-collected lists, which also speeds up $..[…] queries.

New features

  • Full RFC 9535 escape sequences in single- and double-quoted strings: \n, \t, \r, \b, \f, \/, \\, \', \", and \uXXXX. Unknown escapes pass through unchanged so regex patterns can be embedded inline.
  • Unicode member names in dot notation: $.屬性, $.имя, etc. Previously these required bracket notation.
  • Scientific-notation float literals: 1.5e2, -1e-3, 2E+2, -0.123e2.
  • count() built-in (postfix and prefix). Returns the length of a list or the size of a map.
  • Prefix-style function calls in filters: length(@), count(@.x), sum(@.nums), min(@.scores), max(@.scores), avg(@.values), concat(@.parts), match(@.s, "regex"), search(@.s, "regex").
  • match() and search() regex built-ins. match() requires the whole string to match (RFC 9535 semantics); search() matches anywhere. Invalid regex patterns return false instead of raising.
  • JSONPathEx.evaluate!/2 — raises on parse failure and returns the evaluation result directly.

Code quality

  • Removed dead min: 1 option passes to NimbleParsec.repeat/3 (the option was silently ignored).
  • Dropped unwrap_and_tag from punctuation combinators that are then wrapped in ignore ([, ], (, ), ,, :, ', ").
  • Removed unused is_list(result) branch from multi-key bracket access on lists (get/2 on a list always returns a list).
  • Improved internal sentinels (@missing) are namespaced (:"$$__jsonpath_ex_missing__$$") to avoid collision with user-supplied atoms.

Test suite

  • 116 → 206 tests, including:
    • test/jsonpath_ex/regression_test.exs — one test per fixed bug, asserting the corrected behavior.
    • test/jsonpath_ex/feature_test.exs — coverage for every new feature.
    • test/jsonpath_ex/stress_test.exs@tag :performance deep/wide stress cases (run with mix test --include performance).

Benchmarks

  • New bench/ directory with Benchee suites for parsing, evaluation, slicing, filters, and recursion. Run any of them with mix run bench/<file>.exs.

0.2.0

Initial public release on Hex.