Last active
October 24, 2023 22:13
-
-
Save kipcole9/0bd4c6fb6109bfec9955f785087f53fb to your computer and use it in GitHub Desktop.
Helpers for Elixir Maps: underscore, atomise and stringify map keys
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Map.Helpers do | |
@moduledoc """ | |
Functions to transform maps | |
""" | |
@doc """ | |
Convert map string camelCase keys to underscore_keys | |
""" | |
def underscore_keys(nil), do: nil | |
def underscore_keys(map = %{}) do | |
map | |
|> Enum.map(fn {k, v} -> {Macro.underscore(k), underscore_keys(v)} end) | |
|> Enum.map(fn {k, v} -> {String.replace(k, "-", "_"), v} end) | |
|> Enum.into(%{}) | |
end | |
# Walk the list and atomize the keys of | |
# of any map members | |
def underscore_keys([head | rest]) do | |
[underscore_keys(head) | underscore_keys(rest)] | |
end | |
def underscore_keys(not_a_map) do | |
not_a_map | |
end | |
@doc """ | |
Convert map string keys to :atom keys | |
""" | |
def atomize_keys(nil), do: nil | |
# Structs don't do enumerable and anyway the keys are already | |
# atoms | |
def atomize_keys(struct = %{__struct__: _}) do | |
struct | |
end | |
def atomize_keys(map = %{}) do | |
map | |
|> Enum.map(fn {k, v} -> {String.to_atom(k), atomize_keys(v)} end) | |
|> Enum.into(%{}) | |
end | |
# Walk the list and atomize the keys of | |
# of any map members | |
def atomize_keys([head | rest]) do | |
[atomize_keys(head) | atomize_keys(rest)] | |
end | |
def atomize_keys(not_a_map) do | |
not_a_map | |
end | |
@doc """ | |
Convert map atom keys to strings | |
""" | |
def stringify_keys(nil), do: nil | |
def stringify_keys(map = %{}) do | |
map | |
|> Enum.map(fn {k, v} -> {Atom.to_string(k), stringify_keys(v)} end) | |
|> Enum.into(%{}) | |
end | |
# Walk the list and stringify the keys of | |
# of any map members | |
def stringify_keys([head | rest]) do | |
[stringify_keys(head) | stringify_keys(rest)] | |
end | |
def stringify_keys(not_a_map) do | |
not_a_map | |
end | |
@doc """ | |
Deep merge two maps | |
""" | |
def deep_merge(left, right) do | |
Map.merge(left, right, &deep_resolve/3) | |
end | |
# Key exists in both maps, and both values are maps as well. | |
# These can be merged recursively. | |
defp deep_resolve(_key, left = %{}, right = %{}) do | |
deep_merge(left, right) | |
end | |
# Key exists in both maps, but at least one of the values is | |
# NOT a map. We fall back to standard merge behavior, preferring | |
# the value on the right. | |
defp deep_resolve(_key, _left, right) do | |
right | |
end | |
end |
@mmendez512 the whole point of String.to_existing_atom is for when you cannot not trust the input data, such as when it is sent from a web browser. Catching the exception and falling back to to_atom
is redundant, you may as well just use to_atom
when you trust the input data, and you can just let it fail when you do not trust the input data and get non-matching keys.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you!