defmodule Useful do @moduledoc """ A Library of `Useful` functions for building `Elixir` Apps. """ @doc """ `atomize_map_keys/1` converts a `Map` with different keys to a map with just atom keys. Works recursively for nested maps. Inspired by stackoverflow.com/questions/31990134 ## Examples iex> Useful.atomize_map_keys(%{"name" => "alex", id: 1}) %{id: 1, name: "alex"} iex> Useful.atomize_map_keys(%{"name" => "alex", data: %{ "age" => 17}}) %{name: "alex", data: %{age: 17}} """ def atomize_map_keys(map) when is_map(map) do for {key, val} <- map, into: %{} do cond do is_atom(key) -> {key, atomize_map_keys(val)} true -> {String.to_atom(key), atomize_map_keys(val)} end end end def atomize_map_keys(value), do: value @doc """ `flatten_map/1` flattens a `Map` of any depth Inspired by: https://stackoverflow.com/questions/39401947/flatten-nested-map ## Examples iex> map = %{name: "alex", data: %{age: 17, height: 185}} iex> Useful.flatten_map(map) %{data__age: 17, data__height: 185, name: "alex"} """ def flatten_map(map) when is_map(map) do map |> to_list_of_tuples |> Enum.into(%{}) end defp to_list_of_tuples(map) do map |> Enum.map(&process/1) |> List.flatten() end # avoids ** (Protocol.UndefinedError) protocol Enumerable # not implemented for ~U[2016-05-24 13:26:08.003Z] of type DateTime defp process({key, %Date{} = date}), do: return({key, date}) defp process({key, %DateTime{} = datetime}), do: return({key, datetime}) defp process({key, sub_map}) when is_map(sub_map) do for {sub_key, value} <- flatten_map(sub_map) do {String.to_atom("#{key}__#{sub_key}"), value} end end defp process({key, value}), do: return({key, value}) # defp process(value), do: value defp return({key, value}) do {String.to_atom("#{key}"), value} end end