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

create(haystack \\ nil, path, value)

Specs

create(haystack | nil, path :: binary, value :: any) :: haystack

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 } ]
escape(key)

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!
escaped?(key)

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
get(haystack, path)

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!
keys(notation)

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!
recurse(haystack, handler, prefix \\ "")

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
reduce(haystack, acc, handler, prefix \\ "")

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