# `Moar.Map`
[🔗](https://github.com/synchronal/moar/blob/main/lib/map.ex#L1)

Map-related functions.

# `atomize_key`

```elixir
@spec atomize_key(map(), binary() | atom(), (any() -&gt; any()) | nil) :: map()
```

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"}
```

# `atomize_key!`

```elixir
@spec atomize_key!(map(), binary() | atom(), (any() -&gt; any()) | nil) :: map()
```

Like `atomize_key/3` but raises if `key` is not in `map`.

# `atomize_keys`

```elixir
@spec atomize_keys(map()) :: map()
```

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}
```

# `compact`

```elixir
@spec compact(map()) :: map()
```

Removes keys from a map where the value is nil.

```elixir
iex> Moar.Map.compact(%{a: 1, b: nil, c: ""})
%{a: 1, c: ""}
```

# `compact_blank`

```elixir
@spec compact_blank(map()) :: map()
```

Removes keys from a map where the value is blank.

```elixir
iex> Moar.Map.compact_blank(%{a: 1, b: nil, c: "", d: []})
%{a: 1}
```

# `deep_atomize_keys`

```elixir
@spec deep_atomize_keys(list() | map()) :: map()
```

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}]}
```

# `deep_from_struct`

```elixir
@spec deep_from_struct(struct() | map() | [struct() | map()]) :: map() | [map()]
```

Recursively converts structs to maps, including nested structs and lists of structs.
This is particularly useful for converting structs to plain maps for JSON serialization.

# `deep_merge`

```elixir
@spec deep_merge(map() | keyword(), map() | keyword(), (any(), any() -&gt; any())) ::
  map()
```

Deeply merges two maps into a single map. (It will also accept keyword lists and convert them to maps, as long
as they are not empty lists.)

Optionally accepts `conflict_fn` which gets called when both enumerables have values at the same keypath.
It receives the conflicting values from each map and is expected to return the winning value.

```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}}

iex> Moar.Map.deep_merge(%{a: %{b: 1}}, %{a: %{b: 2}})
%{a: %{b: 2}}

iex> Moar.Map.deep_merge(%{a: %{b: 1}}, %{a: %{b: 2}}, fn x, _y -> x end)
%{a: %{b: 1}}

iex> Moar.Map.deep_merge(%{a: %{b: 1}}, %{a: %{b: 2}}, fn x, y -> [x, y] end)
%{a: %{b: [1, 2]}}
```

# `deep_stringify_keys`

```elixir
@spec deep_stringify_keys(list() | map()) :: map()
```

Converts keys to strings, traversing through descendant lists and maps.

Raises if converting a key would result in a key conflict.

```elixir
iex> Moar.Map.deep_stringify_keys(%{a: %{aa: 1}, b: [%{bb: 2}, %{bbb: 3}]})
%{"a" => %{"aa" => 1}, "b" => [%{"bb" => 2}, %{"bbb" => 3}]}
```

# `deep_take`

```elixir
@spec deep_take(
  map(),
  [atom() | binary() | {atom() | binary(), list() | map()}] | map()
) :: map()
```

Takes keys from nested maps.

```elixir
iex> Moar.Map.deep_take(%{a: 1, b: %{c: 2, d: 3}, z: 9}, [:a, b: [:c]])
%{a: 1, b: %{c: 2}}
```

# `index_by`

```elixir
@spec index_by([map()], any()) :: map()
```

Converts a list of maps into a map of maps indexed by the values of one of the map keys.
It's a map-specific version of the more generic `Moar.Enum.index_by/2`.

```elixir
iex> Moar.Map.index_by([%{name: "Alice", tid: "alice"}, %{name: "Billy", tid: "billy"}], :tid)
%{"alice" => %{name: "Alice", tid: "alice"}, "billy" => %{name: "Billy", tid: "billy"}}
```

# `merge`

```elixir
@spec merge(Enum.t() | nil, Enum.t() | nil) :: map()
```

Merges two enumerables into a single map. Supports `nil` values for either enumerable.

```elixir
iex> Moar.Map.merge(%{a: 1}, [b: 2])
%{a: 1, b: 2}

iex> Moar.Map.merge(nil, [a: 1])
%{a: 1}
```

# `merge_if_blank`

```elixir
@spec merge_if_blank(map(), map()) :: map()
```

Merges two maps, retaining any existing non-blank values.

```elixir
iex> Moar.Map.merge_if_blank(%{a: 1, b: nil, c: ""}, %{a: 100, b: 2, c: 3, d: 4})
%{a: 1, b: 2, c: 3, d: 4}
```

# `put_if_blank`

```elixir
@spec put_if_blank(map() | keyword(), any(), any()) :: map()
```

Puts a key/value pair into the given map if the key is not alredy in the map, or if the value in the map is
blank as defined by `Moar.Term.blank?/1`.

Also, the `map` parameter can be any enumerable that can be turned into a map via `Enum.into/2`.

```elixir
iex> %{a: 1} |> Moar.Map.put_if_blank(:b, 2)
%{a: 1, b: 2}

iex> %{a: 1, b: nil} |> Moar.Map.put_if_blank(:b, 2)
%{a: 1, b: 2}

iex> %{a: 1, b: 3} |> Moar.Map.put_if_blank(:b, 2)
%{a: 1, b: 3}
```

# `put_new!`

```elixir
@spec put_new!(map(), any(), any()) :: map()
```

Like `Map.put_new/3` but raises if `key` already exists in `map`.

```elixir
iex> Moar.Map.put_new!(%{a: 1}, :b, 2)
%{a: 1, b: 2}

iex> Moar.Map.put_new!(%{a: 1, b: 2}, :b, 3)
** (RuntimeError) Map already contains key: :b
```

# `rename_key`

```elixir
@spec rename_key(
  map(),
  {binary(), binary()}
) :: map()
```

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"}
```

# `rename_key`

```elixir
@spec rename_key(map(), binary() | atom(), binary() | atom()) :: map()
```

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"}
```

# `rename_key!`

```elixir
@spec rename_key!(
  map(),
  {binary(), binary()}
) :: map()
```

Like `rename_key/2` but raises if `key` is not in `map`

# `rename_key!`

```elixir
@spec rename_key!(map(), binary() | atom(), binary() | atom()) :: map()
```

Like `rename_key/3` but raises if `key` is not in `map`

# `rename_keys`

```elixir
@spec rename_keys(map(), map()) :: map()
```

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"}
```

# `rename_keys!`

```elixir
@spec rename_keys!(map(), map()) :: map()
```

Like `rename_keys/2` but raises if any key in `keys_map` is not in `map`.

# `stringify_keys`

```elixir
@spec stringify_keys(map()) :: map()
```

Converts keys in `map` to strings.

```elixir
iex> Moar.Map.stringify_keys(%{a: 1, b: 2} )
%{"a" => 1, "b" => 2}
```

# `transform`

```elixir
@spec transform(map(), atom() | binary() | list(), (any() -&gt; any())) :: map()
```

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"}
```

# `validate_keys!`

```elixir
@spec validate_keys!(map(), list() | map() | struct()) :: map()
```

Validates that the keys of `map` are equal to or a subset of `valid_keys`. In addition to being a list, `valid_keys`
can be a map or struct, in which case the keys of the map or struct are used as the list of valid keys.

It returns the input map, or raises an exception if there are non-allowed keys.

```elixir
iex> Moar.Map.validate_keys!(%{a: 1, b: 2}, [:a, :b, :c])
%{a: 1, b: 2}

iex> Moar.Map.validate_keys!(%{a: 1, b: 2}, %{a: nil, b: 2, c: :foo})
%{a: 1, b: 2}

iex> Moar.Map.validate_keys!(%{a: 1, b: 2}, [:a, :c])
** (ArgumentError) Non-allowed keys found in map: [:b]
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
