View Source MapWithIndifferentAccess (map_with_indifferent_access v1.0.0)

Utility functions making it easier to work with maps which can either have atom keys or string keys, but never both.

For example, you'll find it useful when working with Ecto.Changeset, as Ecto.Changeset.cast/4 forbids passing maps with mixed key types.

Nearly all functions mimic Map interface, e.g. MapWithIndifferentAccess.put/3 works just as Map.put/3, with two important differences:

  1. You need to use atom keys when calling the functions. (Even if the map uses string keys.)

  2. If the map uses string keys, key argument will be converted to a string, and only then called with a respective Map function.

usage-example

Usage example

  1. Add map_with_indifferent_access to your list of dependencies in mix.exs:
def deps do
  [
    {:map_with_indifferent_access, "~> 1.0.0"}
  ]
end
  1. Use MapWithIndifferentAccess instead of Map, where you are interacting with a map that can have either string or atom keys:
MapWithIndifferentAccess.get(%{a: 1}, :a) // # returns 1
MapWithIndifferentAccess.get(%{"a" => 1}, :a) // # returns 1

MapWithIndifferentAccess.put(%{a: 1}, :b, 2) // # returns %{a: 1, b: 2}
MapWithIndifferentAccess.put(%{"a" => 1}, :b, 2) // # returns %{"a" => 1, "b" => 2}

# Real world usage example
defmodule ProductController do
  def create(conn, params) do
    params
    |> MapWithIndifferentAccess.put(:author_id, conn.assigns.current_user.id)
    |> ProductService.create()
  end
end

defmodule AdminController do
  def create_test_product(conn, params) do
    %{name: "test product"}
    |> MapWithIndifferentAccess.put(:author_id, conn.assigns.current_user.id)
    |> ProductService.create()
  end
end

Link to this section Summary

Link to this section Functions

@spec delete(map :: map(), key :: atom()) :: map()
@spec fetch!(map :: map(), key :: atom()) :: any()
@spec fetch(map :: map(), key :: atom()) :: {:ok, any()} | :error
Link to this function

get(map, key, default \\ nil)

View Source
@spec get(map :: map(), key :: atom(), default :: any()) :: any()
Link to this function

get_and_update!(map, key, fun)

View Source
@spec get_and_update!(map :: map(), key :: atom(), value :: any()) ::
  {current_value :: any(), new_map :: map()}
Link to this function

get_and_update(map, key, fun)

View Source
@spec get_and_update(map :: map(), key :: atom(), value :: any()) ::
  {current_value :: any(), new_map :: map()}
@spec get_lazy(map :: map(), key :: atom(), value :: (() -> any())) :: any()
@spec has_key?(map :: map(), key :: atom()) :: boolean()
@spec put(map :: map(), key :: atom(), value :: any()) :: map()
Link to this function

put_new(map, key, value)

View Source
@spec put_new(map :: map(), key :: atom(), value :: any()) :: map()
Link to this function

put_new_lazy(map, key, value)

View Source
@spec put_new_lazy(map :: map(), key :: atom(), value :: (() -> any())) :: map()
Link to this function

replace!(map, key, value)

View Source
@spec replace!(map :: map(), key :: atom(), value :: any()) :: map()
Link to this function

replace(map, key, value)

View Source
@spec replace(map :: map(), key :: atom(), value :: any()) :: map()
Link to this function

update!(map, key, value)

View Source
@spec update!(map :: map(), key :: atom(), value :: any()) :: map()
Link to this function

update(map, key, default, value)

View Source
@spec update(map :: map(), key :: atom(), default :: any(), value :: any()) :: map()
@spec uses_string_keys?(map :: map()) :: boolean()

Tries to deduce if the map uses string keys.

It does so by obtaining a random map key and verifying if it's a string.

Thus, if a map contains mixed key types (both strings and atoms), it may return either true and false, randomly. We don't take it as a problem, as this module is not meant to be used with such maps. (It should be used only with maps where all keys are strings or all keys are atoms.)