dot-notes v1.0.0 DotNotes
This module provides several functions to deal with Maps and Lists and various levels of nesting. It is based on the project found at https://github.com/zackehh/dot-notes.
Specifically in Elixir, writing homebrew functions for nesting is quite painful due to both immutability and recursion. DotNotes will remove this pain by adding a very simple interface to achieve just that.
Please note that this module deals with pure JSON structures, and as such Tuples are unsupported (although if there’s a demand I may add them in future).
Summary
Functions
Creates a key/value pair inside a haystack using a path
Escapes a binary to a valid notation form
Checks if a key is already valid notation
Retrieves a potentially nested key from a haystack
Parses a binary notation into a list of keys
Iterates a haystack, nesting appropriately and feeding all values to a handler
Same as recurse/3, but allows the passing of an accumulator
Types
haystack :: %{} | []
Functions
Creates a key/value pair inside a haystack using a path.
If the path is not a valid binary, an error will be raised. The returned value is the provided haystack with the key/value created. Any required nests will be created automatically.
If you omit the haystack, or set it to nil, it will be created based upon
the type of the first key in your path.
Examples
iex> DotNotes.create(%{ }, "map.list[0]", :ok)
%{ "map" => %{ "list" => [ :ok ] } }
iex> DotNotes.create([ ], "[0].map", :ok)
[ %{ "map" => :ok } ]
iex> DotNotes.create("map.list[0]", :ok)
%{ "map" => %{ "list" => [ :ok ] } }
iex> DotNotes.create("[0].map", :ok)
[ %{ "map" => :ok } ]
iex> DotNotes.create(%{ "test" => true }, "map.list[0]", :ok)
%{ "map" => %{ "list" => [ :ok ] }, "test" => true }
iex> DotNotes.create([ :ok ], "[1].map", :ok)
[ :ok, %{ "map" => :ok } ]
Specs
escape(key :: binary) :: escaped :: binary
Escapes a binary to a valid notation form.
If a number if provided it is wrapped inside an array index, and if a binary is provided we determine if it’s an accessor or not; if so we return as it, otherwise we escaped all quotes and wrap in special syntax.
Examples
iex> DotNotes.escape(1)
"[1]"
iex> DotNotes.escape("1")
to_string('["1"]')
iex> DotNotes.escape("][")
to_string('["]["]')
iex> DotNotes.escape(%{ })
** (DotNotes.ParseException) Unexpected key value provided!
Specs
escaped?(key :: binary) :: true | false
Checks if a key is already valid notation.
If the value is not a binary, false will be returned. Otherwise you will
receive either true or false based on whether the key is correctly escaped.
Examples
iex> DotNotes.escaped?("[1]")
true
iex> DotNotes.escaped?(to_string('["1"]'))
true
iex> DotNotes.escaped?(to_string('["]["]'))
true
iex> DotNotes.escaped?(".")
false
iex> DotNotes.escaped?(%{ })
false
Specs
get(haystack, path :: binary) :: value :: any
Retrieves a potentially nested key from a haystack.
If the path is not a valid binary, an error will be raised. The returned value
will be the value stored at the end of the path. If the path cannot be traversed,
a nil value will be returned.
Examples
iex> DotNotes.get(%{ "test" => [ 1 ] }, "test[0]")
1
iex> DotNotes.get([ %{ "test" => 1 } ], "[0].test")
1
iex> DotNotes.get([ %{ "test" => 1 } ], "[0][0]")
nil
iex> DotNotes.get("invalid_haystack", "[0][0]")
nil
iex> DotNotes.get(%{ }, "")
** (DotNotes.ParseException) Unable to parse empty string!
Specs
keys(notation :: binary) :: keys :: [binary | number]
Parses a binary notation into a list of keys.
If the notation is invalid, errors will be raised. Array-based keys will be parsed into numbers, and binary keys will remain binary. This is how you can determine what your structures look like.
Examples
iex> DotNotes.keys("this.is.a.test")
[ "this", "is", "a", "test" ]
iex> DotNotes.keys("list[0].test")
[ "list", 0, "test" ]
iex> DotNotes.keys(to_string('list["]["].test'))
[ "list", "][", "test" ]
iex> DotNotes.keys(%{ })
** (DotNotes.ParseException) Unexpected non-string value provided!
iex> DotNotes.keys("")
** (DotNotes.ParseException) Unable to parse empty string!
Specs
recurse(haystack, handler :: function, prefix :: binary) :: :ok
Iterates a haystack, nesting appropriately and feeding all values to a handler.
The handler should be either arity 2 or 3, and accept arguments in the form
of fn(key, value, path). If you have no use for the path argument, drop it
from your function definition and DotNotes won’t generate these paths. This is
a performance boost for heavily nested objects.
A prefix can be provided if you wish to append paths with a custom prefix. This defaults to a blank binary, but it can be useful if you’re parsing nests.
If a valid haystack is not provided, errors will be raised.
Examples
iex> DotNotes.recurse([ 1, 2, 3 ], fn(key, value) ->
...> Enum.join([ key, value ], " : ")
...> end)
:ok
iex> DotNotes.recurse([ 1, 2, 3 ], fn(key, value, path) ->
...> Enum.join([ key, value, path ], " : ")
...> end)
:ok
iex> DotNotes.recurse("", fn(_key, value) -> value end)
** (ArgumentError) Invalid haystack provided to DotNotes.recurse/3
Specs
reduce(haystack, accumulator :: any, handler :: function, prefix :: binary) :: accumulator :: any
Same as recurse/3, but allows the passing of an accumulator.
An accumulator will store the returned value of your handler and pass through to the next. This is a diversion from the JavaScript API due to the fact that it’s the only way to set any variables inside Elixir in this type of iteration.
If a valid haystack is not provided, errors will be raised.
Examples
iex> DotNotes.reduce([ 1, 2, 3 ], 0, fn(_key, value, acc) ->
...> acc + value
...> end)
6
iex> DotNotes.reduce([ 1, 2, 3 ], 0, fn(_key, value, _path, acc) ->
...> acc + value
...> end)
6
iex> DotNotes.reduce("", 0, fn(_key, value) -> value end)
** (ArgumentError) Invalid haystack provided to DotNotes.reduce/4