[![Actions Status](https://github.com/cleidiano/warpath/workflows/build/badge.svg?branch=master)](https://github.com/cleidiano/warpath/actions)
[![Warpath version](https://img.shields.io/hexpm/v/warpath.svg)](https://hex.pm/packages/warpath)
# Warpath
<!-- MDOC !-->
A implementation of Jsonpath expression proposal by [Stefan Goessner](https://goessner.net/articles/JsonPath/) for Elixir.
## Operators
| Operator | Description |
| :------------------------ | :----------------------------------------------------------------- |
| `$` | The root element to query. This starts all path expressions. |
| `@` | The current node being processed by a filter predicate. |
| `*` | Wildcard. All objects/elements regardless their names. |
| `..` | Deep scan, recursive descent. |
| `.name` | Dot-notated child, it support string or atom as keys. |
| `['name']`,`["name"]` | Bracket-notated child, it support string or atom as keys. |
| `[int (,int>)]` | Array index or indexes |
| `[start:end:step]` | Array slice operator. Start index **inclusive**, end index **exclusive**. |
| `[?(expression)]` | Filter expression. Expression must evaluate to a boolean value. |
## Filter operators
All filter operator supported by Warpath have the same behavior of Elixir lang,
it means that it's possible to compare different data types, check the [Elixir getting started](https://elixir-lang.org/getting-started/basic-operators.html)
page for more information about cross comparision on data types.
Filter are expression that must be result on a boolean value, Warpath will use then to retain data when filter a data structure;
a filter expression have it syntax like this `[?( @.category == 'fiction' )]`.
| Operator | Description |
| :----------------------- | :------------------------------------------------------------------ |
| == | left is equal to right |
| === | left is equal to right in strict mode |
| != | left is not equal to right |
| !== | left is not equal to right in strict mode |
| < | left is less than right |
| <= | left is less or equal to right |
| > | left is greater than right |
| >= | left is greater than or equal to right |
| in | left exists in right `[?(@.price in [10, 20, 30])]` |
| and,&& | logical and operator `[?(@.price > 50 and @.price < 100)]` |
| or,|| | logical or operator `[?(@.category == 'fiction' or @.price < 100)]` |
| not | logical not operator `[?(not @.category == 'fiction')]` |
### Functions allowed in filter expression
| Function | Description |
| :------------------ | :--------------------------- |
| is_atom/1 | check if the given expression argument is evaluate to atom |
| is_binary/1 | check if the given expression argument is evaluate to binary |
| is_boolean/1 | check if the given expression argument is evaluate to boolean |
| is_float/1 | check if the given expression argument is evaluate to float |
| is_integer/1 | check if the given expression argument is evaluate to integer |
| is_list/1 | check if the given expression argument is evaluate to list |
| is_map/1 | check if the given expression argument is evaluate to map |
| is_nil/1 | check if the given expression argument is evaluate to nil |
| is_number/1 | check if the given expression argument is evaluate to number |
| is_tuple/1 | check if the given expression argument is evaluate to tuple |
## Examples
### All children
```elixir
#wildcard using bracket-notation
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers[*]")
{:ok, [100, 200, 300]}
#wildcard using dot-notation
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers.*")
{:ok, [100, 200, 300]}
```
### Children lookup by name
```elixir
#Simple string
iex>Warpath.query(%{"category" => "fiction", "price" => 12.99}, "$.category")
{:ok, "fiction"}
#Quoted string
iex>Warpath.query(%{"key with whitespace" => "some value"}, "$.['key with whitespace']")
{:ok, "some value"}
#Simple atom
iex>Warpath.query(%{atom_key: "some value"}, "$.:atom_key")
{:ok, "some value"}
#Quoted atom
iex>Warpath.query(%{"atom key": "some value"}, ~S{$.:'atom key'})
{:ok, "some value"}
#Unicode support
iex>Warpath.query(%{"🌢" => "Elixir"}, "$.🌢")
{:ok, "Elixir"}
#Union
iex>document = %{"key" => "value", "another" => "entry"}
...> Warpath.query(document, "$['key', 'another']")
{:ok, ["value", "entry"]}
```
### Children lookup by index
```elixir
#Positive index
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers[0]")
{:ok, 100}
#Negative index
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers[-1]")
{:ok, 300}
#Union
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers[0, 1]")
{:ok, [100, 200]}
```
### Slice
```elixir
iex> document = [0, 1, 2, 3, 4]
...> Warpath.query(document, "$[0:2:1]")
{:ok, [0, 1]}
#optional start and step param.
iex> document = [0, 1, 2, 3, 4]
...> Warpath.query(document, "$[:2]")
{:ok, [0, 1]}
#Negative start index
iex> document = [0, 1, 2, 3, 4]
...> Warpath.query(document, "$[-2:]")
{:ok, [3, 4]}
```
### Filter
```elixir
# Using logical and operator with is_integer function guard to gain strictness
iex>document = %{
...> "store" => %{
...> "car" => %{"price" => 100_000},
...> "bicycle" => %{"price" => 500}
...> }
...>}
...> Warpath.query(document, "$..*[?( @.price > 500 and is_integer(@.price) )]")
{:ok, [%{"price" => 100000}]}
# Deep path matching
iex> addresses = [
...> %{"address" => %{"state" => "Bahia"}},
...> %{"address" => %{"state" => "São Paulo"}}
...> ]
...> Warpath.query(addresses, "$[?(@.address.state=='Bahia')]")
{:ok,
[
%{
"address" => %{
"state" => "Bahia"
}
}
]}
#has children using named key
iex> document = %{
...> "store" => %{
...> "car" => %{"price" => 100_000},
...> "bicyle" => %{"price" => 500}
...> }
...> }
...> Warpath.query(document, "$..*[?(@.price)]")
{:ok, [%{"price" => 500}, %{"price" => 100_000}]}
#has children using index
iex> document = [ [1,2,3], [0,5], [], [1], 9, [9,8,7] ]
...> Warpath.query(document, "$[?( @[2] )]") # That means give me all list that have index 2.
{:ok, [ [1,2,3], [9,8,7]] }
```
### Recursive descendant
```elixir
#Collect key
iex>document = %{"store" => %{"car" => %{"price" => 100_000}, "bicycle" => %{"price" => 500}}}
...>Warpath.query(document, "$..price")
{:ok, [500, 100_000]}
#Collect index
iex> document = [ [1,2,3], [], :item, [0,5], [1], 9, [9,8,7] ]
...>Warpath.query(document, "$..[2]")
{:ok, [:item, 3, 7]}
#Using filter criteria to scan
iex> document = [ [1,2], [], :item, 9, [9,8], 1.1, "string" ]
...>Warpath.query(document, "$..[?( is_list(@) )]")
{:ok, [ [1, 2], [], [9, 8]]}
```
### Options
```elixir
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers")
{:ok, [100, 200, 300]}
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers[0, 1]", result_type: :path)
{:ok, ["$['integers'][0]", "$['integers'][1]"]}
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers[0]", result_type: :path_tokens)
{:ok, [{:root, "$"}, {:property, "integers"}, {:index_access, 0}]}
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers[0, 1]", result_type: :value_path)
{:ok, [{100, "$['integers'][0]"}, {200, "$['integers'][1]"}]}
iex>document = %{"integers" => [100, 200, 300]}
...> Warpath.query(document, "$.integers[0]", result_type: :value_path_tokens)
{:ok, {100, [{:root, "$"}, {:property, "integers"}, {:index_access, 0}]}}
```
<!-- MDOC !-->
**Installation:**
The package can be installed by adding `warpath` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:warpath, "~> 0.4.1"}
]
end
```
See documentation at [https://hexdocs.pm](https://hexdocs.pm/warpath/Warpath.html).
To see a comparisions between warpath and others json path libraries, visit [json-path-comparison](https://cburgmer.github.io/json-path-comparison/) and see the great job done by `Christoph Burgmer`.