/warpath

Warpath is a implementation of Jsonpath expression for Elixir

Primary LanguageElixirMIT LicenseMIT

Actions Status Warpath version

Warpath

A implementation of Jsonpath expression proposal by Stefan Goessner 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 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

    #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

    #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

    #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

    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

    # 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

    #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

    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}]}}

Installation:

The package can be installed by adding warpath to your list of dependencies in mix.exs:

def deps do
  [
    {:warpath, "~> 0.4.0"}
  ]
end

See documentation at https://hexdocs.pm.

To see a comparisions between warpath and others json path libraries, visit json-path-comparison and see the great job done by Christoph Burgmer.