From 2069958f30cfef4cb6e4168ad5bae03ae63c45df Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Tue, 30 Jul 2024 20:00:10 +0200 Subject: [PATCH] Adds Maps.delete_empty helper that can remove empty data from map/structs And adds Maps.map_all helper to map a function of all nested key/value pairs --- lib/wuunder_utils/maps.ex | 108 ++++++++++++++++++++++++++++++++++ lib/wuunder_utils/presence.ex | 15 +++++ mix.exs | 2 +- 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/lib/wuunder_utils/maps.ex b/lib/wuunder_utils/maps.ex index 229a5c8..ca05145 100644 --- a/lib/wuunder_utils/maps.ex +++ b/lib/wuunder_utils/maps.ex @@ -1046,6 +1046,114 @@ defmodule WuunderUtils.Maps do end) end + @doc """ + Trims an incoming map/list/tuple. Removes all keys that have a `nil` value. Structs with an empty value + will reset to the default value. Items in lists and tuples that contain nil values will be deleted. + + ## Examples + + iex> WuunderUtils.Maps.delete_empty(%{name: nil, last_name: "Jansen"}) + %{last_name: "Jansen"} + + iex> WuunderUtils.Maps.delete_empty(%{name: nil, last_name: nil}) + nil + + iex> WuunderUtils.Maps.delete_empty({1, 2, nil, 3, 4}) + {1, 2, 3, 4} + + iex> WuunderUtils.Maps.delete_empty([1, 2, nil, 3, 4]) + [1, 2, 3, 4] + + iex> WuunderUtils.Maps.delete_empty(%{items: [%{a: nil, b: nil}, %{a: 1, b: 2}]}) + %{items: [%{a: 1, b: 2}]} + + iex> WuunderUtils.Maps.delete_empty(%{items: [%{a: [1, nil, %{x: 1337, y: {1, nil, 2, {nil, nil}}}], b: nil}, %{a: 1, b: 2}]}) + %{items: [%{a: [1, %{y: {1, 2}, x: 1337}]}, %{a: 1, b: 2}]} + """ + @spec delete_empty(any()) :: any() + def delete_empty(value) when is_map(value) do + new_map = + value + |> Map.keys() + |> Enum.reduce(value, fn key, new_map -> + value = Map.get(new_map, key) + new_value = delete_empty(value) + + if is_nil(new_value) do + delete_field(new_map, key) + else + put_field(new_map, key, new_value) + end + end) + + cond do + is_struct(new_map) -> new_map + is_map(new_map) && Enum.empty?(Map.keys(new_map)) -> nil + true -> new_map + end + end + + def delete_empty(list) when is_list(list) do + new_list = + list + |> Enum.map(&delete_empty/1) + |> Enum.reject(&is_nil/1) + + if Enum.any?(new_list) do + new_list + else + nil + end + end + + def delete_empty(tuple) when is_tuple(tuple) do + new_tuple = + tuple + |> Tuple.to_list() + |> Enum.map(&delete_empty/1) + |> Enum.reject(&is_nil/1) + + if Enum.any?(new_tuple) do + List.to_tuple(new_tuple) + else + nil + end + end + + def delete_empty(value), do: value + + @doc """ + Maps a given function over entire structure (map/list/struct/tuple) + + ## Examples + + iex> WuunderUtils.Maps.map_all(%{name: " test ", data: ["some item", "other item ", %{x: " value"}]}, &WuunderUtils.Presence.trim/1) + %{data: ["some item", "other item", %{x: "value"}], name: "test"} + """ + @spec map_all(any(), function()) :: any() + def map_all(value, map_fn) when is_map(value) and is_function(map_fn) do + value + |> Map.keys() + |> Enum.reduce(value, fn key, new_map -> + value = Map.get(new_map, key) + new_value = map_all(value, map_fn) + + put_field(new_map, key, new_value) + end) + end + + def map_all(values, map_fn) when is_list(values) and is_function(map_fn), + do: Enum.map(values, &map_all(&1, map_fn)) + + def map_all(values, map_fn) when is_tuple(values) and is_function(map_fn) do + values + |> Tuple.to_list() + |> map_all(map_fn) + |> List.to_tuple() + end + + def map_all(value, map_fn), do: map_fn.(value) + defp transform_struct(module, struct, transform) do if has_ecto_schema?(module) do module diff --git a/lib/wuunder_utils/presence.ex b/lib/wuunder_utils/presence.ex index 321a0bf..3573f0e 100644 --- a/lib/wuunder_utils/presence.ex +++ b/lib/wuunder_utils/presence.ex @@ -67,4 +67,19 @@ defmodule WuunderUtils.Presence do """ @spec empty?(t()) :: boolean() def empty?(value), do: any?(value) == false + + @doc """ + Optionally trims data + + ## Examples + + iex> WuunderUtils.Presence.trim(" test ") + "test" + + iex> WuunderUtils.Presence.trim(nil) + nil + """ + @spec trim(any()) :: any() + def trim(value) when is_binary(value), do: String.trim(value) + def trim(value), do: value end diff --git a/mix.exs b/mix.exs index 357063c..81b420f 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,7 @@ defmodule WuunderUtils.MixProject do start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], preferred_cli_env: [coveralls: :test, "coveralls.html": :test], - version: "0.7.0" + version: "0.8.0" ] end