JSONPathEx

ElixirModule VersionHex Docs

A fast Elixir library for parsing and evaluating JSONPath expressions, with RFC 9535-aligned filter semantics.


Features


Installation

def deps do
[
{:jsonpath_ex, "~> 0.3.0"}
]
end

Then run:

mix deps.get

Usage

Evaluating expressions

iex> json = %{
...> "store" => %{
...> "book" => [
...> %{"category" => "reference", "author" => "Nigel Rees", "price" => 8.95},
...> %{"category" => "fiction", "author" => "Evelyn Waugh", "price" => 12.99}
...> ]
...> }
...> }
iex> JSONPathEx.evaluate("$.store.book[*].author", json)
{:ok, ["Nigel Rees", "Evelyn Waugh"]}

Filters with prefix functions and regex

iex> data = [
...> %{"name" => "alice", "score" => 90},
...> %{"name" => "bob", "score" => 50},
...> %{"name" => "alex", "score" => 75}
...> ]
iex> JSONPathEx.evaluate(~S{$[?(search(@.name, "^al") && @.score > 70)]}, data)
{:ok, [%{"name" => "alice", "score" => 90}, %{"name" => "alex", "score" => 75}]}

Parse separately for re-use

If you're evaluating the same path against many JSON documents, parse once:

{:ok, ast} = JSONPathEx.Parser.parse("$.store.book[*].title")
JSONPathEx.Evaluator.evaluate(ast, json1)
JSONPathEx.Evaluator.evaluate(ast, json2)

evaluate!/2

iex> JSONPathEx.evaluate!("$[*]", [1, 2, 3])
[1, 2, 3]
iex> JSONPathEx.evaluate!("invalid", %{})
** (ArgumentError) JSONPathEx: parse error at line 1: expected string "$"

Supported syntax

SelectorExampleNotes
Root$
Current node@only inside filters
Dot child$.keyunicode names supported
Quoted dot child$."key with spaces", $.'k'
Bracket child$['key'], $["k"]escapes supported
Multi-key bracket$['a','b','c']
Wildcard$.*, $[*]
Array index$[0], $[-1]negative indexing supported
Multi-index$[0,1,3]
Slice$[1:5], $[::2], $[::-1]RFC 9535 semantics
Recursive descent$..key, $..*
Filter$[?(@.price < 10)]also shorthand $[?@.x]
Nested filter$[?(@.tags[?(@.name == "x")])]
Grouping`$[?((@.a
Comparisons==!====<<=>>=innin
Logical&&||! (prefix only)
Arithmetic+ - * / %left-associative
Postfix functions@.items.length()
Prefix functionslength(@), match(@.s, "\\d+")

Filter semantics

JSONPathEx follows RFC 9535-style "Nothing" semantics inside filters:

ExpressionOn %{"v" => nil}On %{} (missing)
@.v == nulltruefalse
@.v != nullfalsetrue
@.missing == @.also_missingn/atrue
@.v > 5 (with @.v = "x")false (mixed types)false
@.v / 0filter excludes (no match)filter excludes

This means:


Benchmarks

The bench/ directory contains Benchee suites:

mix run bench/parsing.exs # parser throughput
mix run bench/evaluation.exs # bookstore-document workloads
mix run bench/slicing.exs # array slicing on 100/10k/100k lists
mix run bench/filters.exs # filter throughput on 1k/10k items
mix run bench/recursion.exs # deep-scan on deep/wide structures

Indicative results (Apple Silicon, single core):

Workloadv0.3.0
Parse $.store.book[?(@.price > 10 && @.category == "fiction")]~10 µs
$[::-1] on a 100,000-element list~5 ms
$[?(@.id > 100)] on 10,000 maps~10 ms
$[?(@.value > $[0].value)] on 10,000 maps~13 ms

(v0.2.0 took ~9 s for $[::2] on 100k and ~340 ms for the root-reference filter — see CHANGELOG.md.)


Contributing

mix test # fast unit tests
mix test --include performance # include stress + performance tests
mix run bench/parsing.exs # one of the benchmark suites

PRs welcome.

License

MIT — see the LICENSE file for details.