Toolbox.Utils.Map (toolbox v1.1.0)

A set of utility functions for Maps.

Link to this section Summary

Functions

Creates deep map with value on key represented by path. If path is empty, it returns given value.

Deeply updates left map by right map. Existing keys from left map not contained in right map are unchanged. New keys from right map are added. If the key is present in both maps and both values are maps, maps are deeply merges. If the key is present in both maps and value of one of them is not a map, value of key in left map is replaced by value in right map.

Removes key represented as path (list of keys) deeply in map if it exists.

Converts map from flat to nested map.

Follows the path in the (deep) map, defaulting to default if some of the objects on the path is missing.

Extracts all (json-like) paths in the map.

Subtracts map from another map, the leaf-paths from the second map are removed from the first.

Converts regular nested map to flat map.

Link to this section Functions

Link to this function

deep_create(list, value)

@spec deep_create(path :: list(), value :: any()) :: map() | any()

Creates deep map with value on key represented by path. If path is empty, it returns given value.

examples

Examples

iex> Toolbox.Utils.Map.deep_create([], :some_value)
:some_value

iex> Toolbox.Utils.Map.deep_create(["a", :b], :other_value)
%{"a" => %{:b => :other_value}}
Link to this function

deep_merge(left, right)

@spec deep_merge(map(), map()) :: map()

Deeply updates left map by right map. Existing keys from left map not contained in right map are unchanged. New keys from right map are added. If the key is present in both maps and both values are maps, maps are deeply merges. If the key is present in both maps and value of one of them is not a map, value of key in left map is replaced by value in right map.

example

Example

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

iex> Toolbox.Utils.Map.deep_merge(%{a: %{b: 1}}, %{a: %{c: 3}})
%{a: %{b: 1, c: 3}}

iex> Toolbox.Utils.Map.deep_merge(%{a: %{b: %{c: 1}}}, %{a: %{b: %{d: 2}}})
%{a: %{b: %{c: 1, d: 2}}}

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

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

iex> Toolbox.Utils.Map.deep_merge(%{a: 1}, %{a: 2})
%{a: 2}
Link to this function

deep_remove(map, path)

@spec deep_remove(map(), list()) :: map()

Removes key represented as path (list of keys) deeply in map if it exists.

example

Example

iex> Toolbox.Utils.Map.deep_remove(%{a: 1, b: 2}, [])
%{a: 1, b: 2}

iex> Toolbox.Utils.Map.deep_remove(%{a: 1, b: 2}, [:a])
%{b: 2}

iex> Toolbox.Utils.Map.deep_remove(%{a: 1, b: 2}, [:a, :aa])
%{a: 1, b: 2}

iex> Toolbox.Utils.Map.deep_remove(%{a: %{aa: 1, ab: 2}, b: 2}, [:a, :aa])
%{a: %{ab: 2}, b: 2}

iex> Toolbox.Utils.Map.deep_remove(%{a: %{aa: 1, ab: 2}, b: 2}, [:a, :aa, :aaa])
%{a: %{aa: 1, ab: 2}, b: 2}
Link to this function

from_flat_map(flat_map)

Converts map from flat to nested map.

The input map must be flat with array keys, which represent the path to the respective values. The corresponding nested map is then constructed.

Also see to_flat_map/1 which performs the reverse operation.

examples

Examples

iex> Toolbox.Utils.Map.from_flat_map(%{
...>   ["contact", "name"] => "Jored",
...>   ["contact", "address", "city"] => "Lrno",
...>   ["active", "ui"] => true,
...>   ["active", "backend"] => false
...> })
%{
  "contact" => %{
    "name" => "Jored",
    "address" => %{
      "city" => "Lrno"
    }
  },
  "active" => %{
    "ui" => true,
    "backend" => false
  }
}

iex> Toolbox.Utils.Map.from_flat_map(%{
...>   [:very, :very, :very, :very, :very, :very, :deep] => "object",
...>   [:very, :very, :very, :very, :very, :very, :nested] => "map"
...> })
%{
  very: %{
    very: %{
      very: %{
        very: %{
          very: %{
            very: %{
              deep: "object",
              nested: "map"
            }
          }
        }
      }
    }
  }
}

iex> Toolbox.Utils.Map.from_flat_map(%{})
%{}

iex> Toolbox.Utils.Map.from_flat_map(Toolbox.Utils.Map.to_flat_map(%{
...>   "contact" => %{
...>     "name" => "Jonas",
...>     "address" => %{"city" => "Brnp"}},
...>     "favorites" => %{
...>       "food" => %{
...>         1 => ["cornflakes", "corn"],
...>         5 => ["pizza"],
...>         8 => ["pasta"]
...>       }
...>     }
...>   }
...> ))
%{
  "contact" => %{
    "name" => "Jonas",
    "address" => %{"city" => "Brnp"}},
    "favorites" => %{
      "food" => %{
        1 => ["cornflakes", "corn"],
        5 => ["pizza"],
        8 => ["pasta"]
    }
  }
}
Link to this function

get_path(map, path, default \\ nil)

@spec get_path(map(), [binary() | atom()], any()) :: any()

Follows the path in the (deep) map, defaulting to default if some of the objects on the path is missing.

example

Example

iex> object = %{"some" => %{"deep" => %{"object" => "data"}}}
...> Toolbox.Utils.Map.get_path(object, ["some", "deep", "object"])
"data"
iex> Toolbox.Utils.Map.get_path(object, ["non-existent"], "default")
"default"
Link to this function

json_paths(map)

Extracts all (json-like) paths in the map.

Given any artitrarily nested map, the function returns all paths the map contains.

The output is not sorted in a predictable way.

examples

Examples

iex> Toolbox.Utils.Map.json_paths(%{
...>    "contact" => %{
...>      "name" => "Trest",
...>      "phone" => 605554171,
...>      "address" => %{"city" => "Brno"}
...>    },
...>    "active" => false
...> }) |> Enum.sort()
[
  ["active"],
  ["contact", "address", "city"],
  ["contact", "name"],
  ["contact", "phone"],
]

iex> Toolbox.Utils.Map.json_paths(%{"a" => %{1 => %{%{"cool" => "stuff"} => %{atom: :work_too}}}})
[
  ["a", 1, %{"cool" => "stuff"}, :atom]
]

iex> Toolbox.Utils.Map.json_paths(%{})
[]
Link to this function

subtract(map, to_delete)

Subtracts map from another map, the leaf-paths from the second map are removed from the first.

examples

Examples

iex> Toolbox.Utils.Map.subtract(%{a: 1, b: 2}, %{a: true})
%{b: 2}

iex> Toolbox.Utils.Map.subtract(%{a: 1}, %{a: %{b: %{c: true}}})
%{a: 1}

iex> Toolbox.Utils.Map.subtract(
...>   %{a: %{b: %{c: "1"}, d: "2"}, e: "3"},
...>   %{a: %{b: %{c: true}}, e: true}
...> )
%{a: %{b: %{}, d: "2"}}

iex> Toolbox.Utils.Map.subtract(
...>   %{"a" => 1, "b" => %{"c" => true}},
...>   %{"b" => %{"c" => true}}
...> )
%{"a" => 1, "b" => %{}}

iex> Toolbox.Utils.Map.subtract(%{a: 1, b: 2}, %{c: 2, d: 3})
%{a: 1, b: 2}

iex> Toolbox.Utils.Map.subtract(%{a: %{b: 1}}, %{a: true})
%{}
Link to this function

to_flat_map(map)

Converts regular nested map to flat map.

The input map can be any map - arbitrarily nested, the output is a flat map where keys are arrays representing the path to the respective value.

Also see from_flat_map/1 which performs the reverse operation.

examples

Examples

iex> Toolbox.Utils.Map.to_flat_map(%{
...>   "contact" => %{
...>     "name" => "Kert",
...>     "address" => %{"city" => "Krno"}
...>   },
...>   "status" => %{"ui" => %{"active" => true},
...>   "backend" => %{"fluent" => false}}
...> })
%{
  ["contact", "name"] => "Kert",
  ["contact", "address", "city"] => "Krno",
  ["status", "ui", "active"] => true,
  ["status", "backend", "fluent"] => false
}

iex> Toolbox.Utils.Map.to_flat_map(%{good: %{1 => :nice}, very: %{deep: %{%{ish: :map} => :askey}}})
%{
  [:good, 1] => :nice,
  [:very, :deep, %{ish: :map}] => :askey
}

iex> Toolbox.Utils.Map.to_flat_map(%{"regular" => "map"})
%{
  ["regular"] => "map"
}

iex> Toolbox.Utils.Map.to_flat_map(%{})
%{}