Packages
moar
1.10.0
5.0.0
4.3.0
4.2.0
4.1.0
4.0.0
3.2.0
3.1.0
3.0.0
2.7.0
2.6.0
2.5.0
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.1
2.0.0
1.64.0
1.63.1
1.63.0
1.62.0
1.61.2
1.61.1
1.61.0
1.60.0
1.59.2
1.59.1
1.59.0
1.58.0
1.57.0
1.56.2
1.56.1
1.56.0
1.55.0
1.54.0
1.53.0
1.52.1
1.52.0
1.51.0
1.50.0
1.49.0
1.48.0
1.47.0
1.46.0
1.45.1
1.45.0
1.44.0
1.43.0
1.42.0
1.41.0
1.40.0
1.39.0
1.38.0
1.37.0
1.36.0
1.35.0
1.34.0
1.33.0
1.32.0
1.31.0
1.30.0
1.29.0
1.28.0
1.27.0
1.26.0
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.0
1.19.3
1.19.2
1.19.1
1.19.0
1.18.1
1.18.0
1.17.0
1.16.0
1.15.0
1.14.0
1.13.1
1.13.0
1.12.0
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.0
1.5.0
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.1.0
A dependency-free utility library containing 100+ useful functions.
Current section
Files
Jump to
Current section
Files
lib/map.ex
defmodule Moar.Map do
# @related [test](/test/map_test.exs)
@moduledoc "Map-related functions."
@doc """
Converts `key` in `map` to an atom, optionally transforming the value with `value_transformer`.
Raises if `key` is a string and `map` already has an atomized version of that key.
```elixir
iex> Moar.Map.atomize_key(%{"number-one" => "one", "number-two" => "two"}, "number-one")
%{:number_one => "one", "number-two" => "two"}
iex> Moar.Map.atomize_key(%{"number-one" => "one", "number-two" => "two"}, "number-one", &String.upcase/1)
%{:number_one => "ONE", "number-two" => "two"}
```
"""
@spec atomize_key(map(), binary() | atom(), (any() -> any()) | nil) :: map()
def atomize_key(map, key, value_transformer \\ &Function.identity/1) do
atomized_key =
if is_atom(key) do
key
else
key
|> Moar.String.underscore()
|> Moar.Atom.from_string()
|> tap(fn new_key ->
if Map.has_key?(map, new_key),
do: raise(KeyError, ["key ", inspect(new_key), " already exists in ", inspect(map)] |> to_string())
end)
end
map |> rename_key(key, atomized_key) |> transform(atomized_key, value_transformer)
end
@doc """
Like `atomize_key/3` but raises if `key` is not in `map`.
"""
@spec atomize_key!(map(), binary() | atom(), (any() -> any()) | nil) :: map()
def atomize_key!(map, key, value_fn \\ &Function.identity/1)
def atomize_key!(map, key, value_fn) when is_map_key(map, key),
do: atomize_key(map, key, value_fn)
def atomize_key!(map, key, _value_fn),
do: raise(KeyError, ["key ", inspect(key), " not found in: ", inspect(map)] |> to_string())
@doc """
Converts keys in `map` to atoms.
Raises if converting a key from a string to an atom would result in a key conflict.
```elixir
iex> Moar.Map.atomize_keys(%{"a" => 1, "b" => 2})
%{a: 1, b: 2}
```
"""
@spec atomize_keys(map()) :: map()
def atomize_keys(map),
do: Enum.reduce(map, map, fn {k, _v}, acc -> atomize_key(acc, k) end)
@doc """
Converts keys to atoms, traversing through descendant lists and maps.
Raises if converting a key from a string to an atom would result in a key conflict.
```elixir
iex> Moar.Map.deep_atomize_keys(%{"a" => %{"aa" => 1}, "b" => [%{"bb" => 2}, %{"bbb" => 3}]})
%{a: %{aa: 1}, b: [%{bb: 2}, %{bbb: 3}]}
```
"""
@spec deep_atomize_keys(list() | map()) :: map()
def deep_atomize_keys(list) when is_list(list),
do: list |> Enum.map(&deep_atomize_keys(&1))
def deep_atomize_keys(map) when is_map(map) do
Enum.reduce(map, map, fn
{k, v}, acc when is_map(v) -> atomize_key(acc, k, &deep_atomize_keys/1)
{k, list}, acc when is_list(list) -> atomize_key(acc, k, &Enum.map(&1, fn v -> deep_atomize_keys(v) end))
{k, _v}, acc -> atomize_key(acc, k)
end)
end
def deep_atomize_keys(not_a_map),
do: not_a_map
@doc """
Deeply merges two enumerables into a single map.
```elixir
iex> Moar.Map.deep_merge(%{fruit: %{apples: 3, bananas: 5}, veggies: %{carrots: 10}}, [fruit: [cherries: 20]])
%{fruit: %{apples: 3, bananas: 5, cherries: 20}, veggies: %{carrots: 10}}
```
"""
@spec deep_merge(Enum.t(), Enum.t()) :: map()
def deep_merge(a, b),
do: deep_merge(nil, a, b)
defp deep_merge(_key, a, b) do
if Moar.Protocol.implements?(a, Enumerable) && Moar.Protocol.implements?(b, Enumerable),
do: Map.merge(Enum.into(a, %{}), Enum.into(b, %{}), &deep_merge/3),
else: b
end
@doc """
Merges two enumerables into a single map.
```elixir
iex> Moar.Map.merge(%{a: 1}, [b: 2])
%{a: 1, b: 2}
```
"""
@spec merge(Enum.t(), Enum.t()) :: map()
def merge(a, b) when is_map(a) and is_map(b),
do: Map.merge(a, b)
def merge(a, b),
do: Map.merge(Enum.into(a, %{}), Enum.into(b, %{}))
@doc """
Returns a copy of `map` with `old_key_name` changed to `new_key_name`.
```elixir
iex> %{"color" => "red", "size" => "medium"} |> Moar.Map.rename_key("color", "colour")
%{"colour" => "red", "size" => "medium"}
```
"""
@spec rename_key(map(), binary() | atom(), binary() | atom()) :: map()
def rename_key(map, old_key_name, new_key_name) when is_map_key(map, old_key_name) do
{value, new_map} = Map.pop(map, old_key_name)
Map.put(new_map, new_key_name, value)
end
def rename_key(map, _, _),
do: map
@doc """
Like `rename_key/2` but raises if `key` is not in `map`
"""
@spec rename_key!(map(), {binary(), binary()}) :: map()
def rename_key!(map, {old_key_name, new_key_name} = _old_and_new_key),
do: rename_key!(map, old_key_name, new_key_name)
@doc """
Like `rename_key/3` but raises if `key` is not in `map`
"""
@spec rename_key!(map(), binary() | atom(), binary() | atom()) :: map()
def rename_key!(map, old_key_name, new_key_name) when is_map_key(map, old_key_name),
do: rename_key(map, old_key_name, new_key_name)
def rename_key!(map, old_key_name, _new_key_name),
do: raise(KeyError, ["key ", inspect(old_key_name), " not found in: ", inspect(map)] |> to_string())
@doc """
Returns a copy of `map` with `old_key_name` changed to `new_key_name`.
`old_key_name` and `new_key_name` are passed in as a `{old_key_name, new_key_name}` tuple.
```elixir
iex> %{"color" => "red", "size" => "medium"} |> Moar.Map.rename_key({"color", "colour"})
%{"colour" => "red", "size" => "medium"}
```
"""
@spec rename_key(map(), {binary(), binary()}) :: map()
def rename_key(map, {old_key_name, new_key_name} = _old_and_new_key),
do: rename_key(map, old_key_name, new_key_name)
@doc """
Returns a copy of `map` after changing key names supplied by `keys_map`.
```elixir
%{"behavior" => "chill", "color" => "red"} |> Moar.Map.rename_keys(%{"behavior" => "behaviour", "color" => "colour"})
%{"behaviour" => "chill", "colour" => "red"}
```
"""
@spec rename_keys(map(), map()) :: map()
def rename_keys(map, keys_map),
do: Enum.reduce(keys_map, map, &rename_key(&2, &1))
@doc """
Like `rename_keys/2` but raises if any key in `keys_map` is not in `map`.
"""
@spec rename_keys!(map(), map()) :: map()
def rename_keys!(map, keys_map),
do: Enum.reduce(keys_map, map, &rename_key!(&2, &1))
@doc """
Converts keys in `map` to strings.
```elixir
iex> Moar.Map.stringify_keys(%{a: 1, b: 2} )
%{"a" => 1, "b" => 2}
```
"""
@spec stringify_keys(map()) :: map()
def stringify_keys(map),
do: map |> Map.new(fn {k, v} -> {Moar.Atom.to_string(k), v} end)
@doc """
Transforms values of `map` using `transformer` function.
```elixir
iex> %{"foo" => "chicken", "bar" => "cow", "baz" => "pig"} |> Moar.Map.transform("foo", &String.upcase/1)
%{"foo" => "CHICKEN", "bar" => "cow", "baz" => "pig"}
iex> %{"foo" => "chicken", "bar" => "cow", "baz" => "pig"} |> Moar.Map.transform(["foo", "bar"], &String.upcase/1)
%{"foo" => "CHICKEN", "bar" => "COW", "baz" => "pig"}
```
"""
@spec transform(map(), atom() | binary() | list(), (any() -> any())) :: map()
def transform(map, keys, transformer) when is_list(keys),
do: Enum.reduce(keys, map, fn key, new_map -> transform(new_map, key, transformer) end)
def transform(map, key, transformer) when is_map_key(map, key),
do: Map.update!(map, key, transformer)
def transform(map, _, _),
do: map
end