View Source Warpath (Warpath v0.6.3)

A implementation of Jsonpath expression proposal by Stefan Goessner for Elixir.

The following apis are available: Warpath.query/3, Warpath.delete/2, Warpath.update/3.

operators

Operators

OperatorDescription
$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.
.nameDot-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

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' )].

OperatorDescription
==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
inleft 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)]
notlogical not operator [?(not @.category == 'fiction')]

functions-allowed-in-filter-expression

Functions allowed in filter expression

FunctionDescription
is_atom/1check if the given expression argument is evaluate to atom
is_binary/1check if the given expression argument is evaluate to binary
is_boolean/1check if the given expression argument is evaluate to boolean
is_float/1check if the given expression argument is evaluate to float
is_integer/1check if the given expression argument is evaluate to integer
is_list/1check if the given expression argument is evaluate to list
is_map/1check if the given expression argument is evaluate to map
is_nil/1check if the given expression argument is evaluate to nil
is_number/1check if the given expression argument is evaluate to number
is_tuple/1check if the given expression argument is evaluate to tuple

examples

Examples

all-children

All children

    #wildcard using bracket-notation
    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements[*]")
    {:ok, [:a, :b, :c]}

    #wildcard using dot-notation
    iex> document = %{"integers" => [100, 200, 300]}
    ...> Warpath.query(document, "$.integers.*")
    {:ok, [100, 200, 300]}

children-lookup-by-name

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

Children lookup by index

    #Positive index
    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements[0]")
    {:ok, :a}

    #Negative index
    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements[-1]")
    {:ok, :c}

    #Union
    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements[0, 1]")
    {:ok, [:a, :b]}

slice

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

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" => 100_000}]}

    # 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}, "bicycle" => %{"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

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

Options

    
    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements")
    {:ok, [:a, :b, :c]}

    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements[0, 1]", result_type: :path)
    {:ok, ["$['elements'][0]", "$['elements'][1]"]}

    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements[0]", result_type: :path_tokens)
    {:ok, [{:root, "$"}, {:property, "elements"}, {:index_access, 0}]}

    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements[0, 1]", result_type: :value_path)
    {:ok, [{:a, "$['elements'][0]"}, {:b, "$['elements'][1]"}]}

    iex> document = %{"elements" => [:a, :b, :c]}
    ...> Warpath.query(document, "$.elements[0]", result_type: :value_path_tokens)
    {:ok, {:a, [{:root, "$"}, {:property, "elements"}, {:index_access, 0}]}}

Link to this section Summary

Functions

Remove an item(s) from a nested data structure via the given selector.

The same as query/3, but rise exception if it fail.

Query data for the given expression.

Updates a nested data structure via the given selector.

Link to this section Types

@type container() :: map() | struct() | list()
@type document() :: container() | json()
@type json() :: String.t()
@type opts() :: [
  {:result_type,
   :value | :path | :value_path | :path_tokens | :value_path_tokens}
]
@type selector() :: Warpath.Expression.t() | String.t()
@type update_fun() ::
  (term() -> updated_value())
  | (Warpath.Element.Path.acc(), term() -> updated_value())
@type updated_value() :: any()

Link to this section Functions

Link to this function

delete(document, selector)

View Source
@spec delete(document(), selector()) :: {:ok, container() | nil} | {:error, any()}

Remove an item(s) from a nested data structure via the given selector.

If the selector does not evaluate anything, it returns the data structure unchanged.

This function rely on Access behaviour, that means structs must implement that behaviour to got support.

examples

Examples

iex> users = %{"john" => %{"age" => 27, "country" => "Brasil"}, "meg" => %{"age" => 23, "country" => "U.K"}}
...> Warpath.delete(users, "$..age")
{:ok, %{"john" => %{"country" => "Brasil"}, "meg" => %{"country" => "U.K"}}}

iex> numbers = %{"numbers" => [20, 3, 50, 6, 7]}
...> Warpath.delete(numbers, "$.numbers[?(@ < 10)]")
{:ok, %{"numbers" => [20, 50]}}

iex> numbers = %{"numbers" => [20, 3, 50, 6, 7]}
...> Warpath.delete(numbers, "$")
{:ok, nil}

iex> users = %{"john" => %{"age" => 27}, "meg" => %{"age" => 23}}
...> Warpath.delete(users, "$..city")
{:ok, %{"john" => %{"age" => 27}, "meg" => %{"age" => 23}}} # Unchanged
Link to this function

query!(data, selector, opts \\ [])

View Source
@spec query!(document(), selector(), opts()) :: any()

The same as query/3, but rise exception if it fail.

Link to this function

query(document, selector, opts \\ [])

View Source
@spec query(document(), selector(), opts()) :: {:ok, any()} | {:error, any()}

Query data for the given expression.

example

Example

iex> data_structure = %{"name" => "Warpath"}
...> Warpath.query(data_structure, "$.name")
{:ok, "Warpath"}

iex> raw_json = ~s/{"name": "Warpath"}/
...> Warpath.query(raw_json, "$.name")
{:ok, "Warpath"}

iex> #Pass a compiled expression as selector
...> {:ok, expression} = Warpath.Expression.compile("$.autobots[0]")
...> Warpath.query(%{"autobots" => ["Optimus Prime", "Warpath"]}, expression)
{:ok, "Optimus Prime"}

options

Options:

result_type:

  • :value - return the value of evaluated expression - default
  • :path - return the bracketfiy path string representation of evaluated expression instead of it's value
  • :value_path - return both value and bracketify path string.
  • :path_tokens - return the path tokens instead of it string representation, see Warpath.Element.Path.
  • :value_path_tokens - return both value and path tokens.
Link to this function

update(document, selector, fun)

View Source
@spec update(document(), selector(), update_fun()) ::
  {:ok, container() | updated_value()} | {:error, any()}

Updates a nested data structure via the given selector.

The fun will be called for each item discovered under the given selector, the fun result will be used to update the data structure.

If the selector does not evaluate anything, it returns the data structure unchanged.

This function rely on Access behaviour, that means structs must implement that behaviour to got support.

examples

Examples

iex> users = %{"john" => %{"age" => 27}, "meg" => %{"age" => 23}}
...> Warpath.update(users, "$.john.age", &(&1 + 1))
{:ok, %{"john" => %{"age" => 28}, "meg" => %{"age" => 23}}}

iex> numbers = %{"numbers" => [20, 3, 50, 6, 7]}
...> Warpath.update(numbers, "$.numbers[?(@ < 10)]", &(&1 * 2))
{:ok, %{"numbers" => [20, 6, 50, 12, 14]}}

iex> users = %{"john" => %{"age" => 27}, "meg" => %{"age" => 23}}
...> Warpath.update(users, "$.theo.age", &(&1 + 1))
{:ok, %{"john" => %{"age" => 27}, "meg" => %{"age" => 23}}} # Unchanaged