Localize.Utils.Map (Localize v0.32.0)

Copy Markdown View Source

Functions for transforming maps, keys and values.

This module provides recursive map traversal and transformation utilities including key/value type conversion (atomize, stringify, integerize, floatize), deep merging, key renaming, and camelCase to snake_case conversion.

The primary public API includes:

Summary

Functions

Transforms a map's String.t keys to atom() keys.

Transforms a map's String.t values to atom() values.

Delete all members of a map that have a key in the list of keys.

Extract strings from a map or list.

Transforms a map's String.t keys to Float.t keys.

Transforms a map's String.t values to Float.t values.

Returns an argument unchanged.

Transforms a map's String.t keys to Integer.t keys.

Transforms a map's String.t values to Integer.t values.

Returns the result of deep merging a list of maps.

Prune a potentially deeply nested map of some of its branches.

Removes any leading underscores from map String.t keys.

Rename map keys from from to to.

Transforms a map's atom() keys to String.t keys.

Transforms a map's atom() values to String.t values.

Convert a camelCase string or atom to snake_case.

Convert map String.t keys from camelCase to snake_case.

Functions

atomize_keys(map, options \\ [])

Transforms a map's String.t keys to atom() keys.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3. One additional option applies to this function directly:

    • :only_existing which is set to true will only convert the binary value to an atom if the atom already exists. The default is false.

Returns

  • A map with string keys converted to atom keys.

Examples

iex> Localize.Utils.Map.atomize_keys %{"a" => %{"b" => %{1 => "c"}}}
%{a: %{b: %{1 => "c"}}}

atomize_values(map, options \\ [only_existing: false])

Transforms a map's String.t values to atom() values.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3. One additional option applies to this function directly:

    • :only_existing which is set to true will only convert the binary value to an atom if the atom already exists. The default is false.

Returns

  • A map with string values converted to atom values.

Examples

iex> Localize.Utils.Map.atomize_values %{"a" => %{"b" => %{1 => "c"}}}
%{"a" => %{"b" => %{1 => :c}}}

deep_map(map, function, options \\ [level: %{__struct__: Range, first: 1, last: 1_000_000, step: 1}, filter: [], reject: [], skip: [], only: [], except: []])

@spec deep_map(
  map() | list(),
  function :: function() | {function(), function()},
  options :: list()
) :: map() | list()

Recursively traverse a map and invoke a function that transforms the map for each key/value pair.

Arguments

  • map is any map/0.

  • function is a 1-arity function or function reference that is called for each key/value pair of the provided map. It can also be a 2-tuple of the form {key_function, value_function}.

    • In the case where function is a single function it will be called with the 2-tuple argument {key, value}.

    • In the case where function is of the form {key_function, value_function} the key_function will be called with the argument key and the value function will be called with the argument value.

  • options is a keyword list of options. The default is [level: 1..1000000, filter: [], reject: [], skip: [], only: [], except: []].

Options

  • :level indicates the starting (and optionally ending) levels of the map at which the function is executed. This can be an integer representing one level or a range indicating a range of levels. The default is 1..1000000.

  • :only is a term or list of terms or a check function. If it is a term or list of terms, the function is only called if the key of the map is equal to the term or in the list of terms. If :only is a check function then the check function is passed the {k, v} of the current branch in the map. It is expected to return a truthy value that if true signals that the argument function will be executed.

  • :except is a term or list of terms or a check function. If it is a term or list of terms, the function is only called if the key of the map is not equal to the term or not in the list of terms. If :except is a check function then the check function is passed the {k, v} of the current branch in the map. It is expected to return a truthy value that if true signals that the argument function will not be executed.

  • :filter is a term or list of terms or a check function. If the key currently being processed equals the term (or is in the list of terms, or the check_function returns a truthy value) then this branch of the map is processed by function and its output is included in the result.

  • :reject is a term or list of terms or a check function. If the key currently being processed equals the term (or is in the list of terms, or the check_function returns a truthy value) then this branch of the map is omitted from the mapped output.

  • :skip is a term or list of terms or a check function. If the key currently being processed equals the term (or is in the list of terms, or the check_function returns a truthy value) then this branch of the map is not processed by function but it is included in the mapped result.

Notes

  • :only and :except operate on individual keys whereas :filter and :reject operate on entire branches of a map.

  • If both the options :only and :except are provided then the function is called only when a term meets both criteria. That means that :except has priority over :only.

  • If both the options :filter and :reject are provided then :reject has priority over :filter.

Returns

  • The map transformed by the recursive application of function.

Examples

iex> map = %{a: :a, b: %{c: :c}}
iex> fun = fn
...>   {k, v} when is_atom(k) -> {Atom.to_string(k), v}
...>   other -> other
...> end
iex> Localize.Utils.Map.deep_map map, fun
%{"a" => :a, "b" => %{"c" => :c}}
iex> map = %{a: :a, b: %{c: :c}}
iex> Localize.Utils.Map.deep_map map, fun, only: :c
%{a: :a, b: %{"c" => :c}}
iex> Localize.Utils.Map.deep_map map, fun, except: [:a, :b]
%{a: :a, b: %{"c" => :c}}
iex> Localize.Utils.Map.deep_map map, fun, level: 2
%{a: :a, b: %{"c" => :c}}

deep_merge(left, right, resolver \\ &standard_deep_resolver/3)

Deep merge two maps.

Arguments

  • left is any map/0.

  • right is any map/0.

  • resolver is a 3-arity function used to resolve merge conflicts. The default resolver prefers the right-side value and recursively merges nested maps.

Returns

  • A single map that is the deep merge of left and right.

Examples

iex> Localize.Utils.Map.deep_merge %{a: "a", b: "b"}, %{c: "c", d: "d"}
%{a: "a", b: "b", c: "c", d: "d"}

iex> Localize.Utils.Map.deep_merge %{a: "a", b: "b"}, %{c: "c", d: "d", a: "aa"}
%{a: "aa", b: "b", c: "c", d: "d"}

delete_in(map, keys)

Delete all members of a map that have a key in the list of keys.

Arguments

  • map is any map/0 or keyword list.

  • keys is a list of keys to delete.

Returns

  • The map with all matching keys removed at any depth.

Examples

iex> Localize.Utils.Map.delete_in %{a: "a", b: "b"}, [:a]
%{b: "b"}

extract_strings(map_or_list, options \\ [])

Extract strings from a map or list.

Recursively process the map or list and extract string values from maps and string elements from lists.

Arguments

  • map_or_list is any map/0 or list.

  • options is a keyword list of options (currently unused).

Returns

  • A flattened list of all string values found in the map or list.

floatize_keys(map, options \\ [])

Transforms a map's String.t keys to Float.t keys.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3.

The map key is converted to a float from a String.t only when the key is comprised of a valid float form.

Keys which cannot be converted to a float are returned unchanged.

Returns

  • A map with string keys converted to float keys where possible.

Examples

iex> Localize.Utils.Map.floatize_keys %{a: %{"1.0" => "value"}}
%{a: %{1.0 => "value"}}

iex> Localize.Utils.Map.floatize_keys %{a: %{"1" => "value"}}
%{a: %{1.0 => "value"}}

floatize_values(map, options \\ [])

Transforms a map's String.t values to Float.t values.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3.

The map value is converted to a float from a String.t only when the value is comprised of a valid float form.

Values which cannot be converted to a float are returned unchanged.

Returns

  • A map with string values converted to float values where possible.

Examples

iex> Localize.Utils.Map.floatize_values %{a: %{b: "1.0"}}
%{a: %{b: 1.0}}

iex> Localize.Utils.Map.floatize_values %{a: %{b: "1"}}
%{a: %{b: 1.0}}

identity(x)

Returns an argument unchanged.

Useful when a noop function is required.

Returns

  • The argument unchanged.

Examples

iex> Localize.Utils.Map.identity(:anything)
:anything

integerize_keys(map, options \\ [])

Transforms a map's String.t keys to Integer.t keys.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3.

The map key is converted to an integer from either an atom or String.t only when the key is comprised of integer digits.

Keys which cannot be converted to an integer are returned unchanged.

Returns

  • A map with string keys converted to integer keys where possible.

Examples

iex> Localize.Utils.Map.integerize_keys %{a: %{"1" => "value"}}
%{a: %{1 => "value"}}

integerize_values(map, options \\ [])

Transforms a map's String.t values to Integer.t values.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3.

The map value is converted to an integer from either an atom or String.t only when the value is comprised of integer digits.

Values which cannot be converted to an integer are returned unchanged.

Returns

  • A map with string values converted to integer values where possible.

Examples

iex> Localize.Utils.Map.integerize_values %{a: %{b: "1"}}
%{a: %{b: 1}}

invert(map, options \\ [])

Invert a map.

Requires that the map is a simple map of keys and a list of values or a single non-map value.

Arguments

  • map is any map/0.

  • options is a keyword list of options.

Options

  • :duplicates which determines how duplicate values are handled:
    • nil or false which is the default and means only one value is kept. Map.new/1 is used meaning the selected value is non-deterministic.

    • :keep meaning duplicate values are returned in a list.

    • :shortest means the shortest duplicate is kept. This operates on string or atom values.

    • :longest means the longest duplicate is kept. This operates on string or atom values.

Returns

  • A map with keys and values swapped.

merge_map_list(list, resolver \\ &standard_deep_resolver/3)

Returns the result of deep merging a list of maps.

Arguments

  • list is a list of maps to be merged.

  • resolver is a 3-arity function used to resolve merge conflicts. The default resolver prefers the right-side value and recursively merges nested maps.

Returns

  • A single map that is the result of deep merging all maps in the list.

Examples

iex> Localize.Utils.Map.merge_map_list [%{a: "a", b: "b"}, %{c: "c", d: "d"}]
%{a: "a", b: "b", c: "c", d: "d"}

prune(map, fun)

Prune a potentially deeply nested map of some of its branches.

Arguments

  • map is any map/0.

  • fun is a 1-arity function. Branches for which the function returns a truthy value are removed from the map.

Returns

  • The map with matching branches pruned.

remove_leading_underscores(map, options \\ [])

Removes any leading underscores from map String.t keys.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3.

Returns

  • A map with leading underscores removed from string keys.

Examples

iex> Localize.Utils.Map.remove_leading_underscores %{"a" => %{"_b" => "b"}}
%{"a" => %{"b" => "b"}}

rename_keys(map, from, to, options \\ [])

Rename map keys from from to to.

Arguments

  • map is any map/0.

  • from is any value map key.

  • to is any valid map key.

  • options is a keyword list of options passed to deep_map/3.

Returns

  • A map with keys matching from renamed to to.

Examples

iex> Localize.Utils.Map.rename_keys %{"a" => %{"this_one" => "value"}}, "this_one", "that_one"
%{"a" => %{"that_one" => "value"}}

stringify_keys(map, options \\ [])

Transforms a map's atom() keys to String.t keys.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3.

Returns

  • A map with atom keys converted to string keys.

Examples

iex> Localize.Utils.Map.stringify_keys %{a: %{"1" => "value"}}
%{"a" => %{"1" => "value"}}

stringify_values(map, options \\ [])

Transforms a map's atom() values to String.t values.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3.

Returns

  • A map with atom values converted to string values.

Examples

iex> Localize.Utils.Map.stringify_values %{a: %{"1" => :value}}
%{a: %{"1" => "value"}}

underscore(other)

@spec underscore(string :: String.t() | atom()) :: String.t()

Convert a camelCase string or atom to snake_case.

This is the code of Macro.underscore/1 with modifications. The change is to cater for strings in the format This_That which in Macro.underscore/1 gets formatted as this__that (note the double underscore) when we actually want this_that.

Arguments

  • string is a String.t or atom() to be transformed.

Returns

  • A snake_case String.t.

Examples

iex> Localize.Utils.Map.underscore "thisThat"
"this_that"

iex> Localize.Utils.Map.underscore "This_That"
"this_that"

underscore_keys(map, options \\ [])

Convert map String.t keys from camelCase to snake_case.

Arguments

  • map is any map/0.

  • options is a keyword list of options passed to deep_map/3.

Returns

  • A map with camelCase string keys converted to snake_case.

Examples

iex> Localize.Utils.Map.underscore_keys %{"a" => %{"thisOne" => "value"}}
%{"a" => %{"this_one" => "value"}}