Reputation: 75760
In Elixir I can check if a variable is a map
or a struct
, by calling Kernel.is_map/1
which makes sense because Structs are Maps underneath, but I'd like to differentiate between the two. I know that I can call __struct__
on a Struct to get it's module name but calling it on a normal map throws:
** (KeyError) key :__struct__ not found in: %{}
So my question is, How do I check if a variable is a map or a struct?
Example use case:
# I want to handle struct and map inputs differently in my Module
defmodule DifferentThings do
def do_something(arg) when is_map(arg) do
# Do something with Maps
end
def do_something(arg) when is_struct(arg) do
# But handle Structs differently
# Issue is, `is_struct` does not exist
end
end
Upvotes: 17
Views: 8999
Reputation: 4361
As Sheharyar noted, Elixir 1.10.0 introduces the is_struct/1
guard. Therfore, your original code will work.
Upvotes: 3
Reputation: 75760
While the simplest way to check if a term is a map
is to use:
Map.has_key?(map, :__struct__)
It does not work in a guard so we had to previously resort to pattern match the :__struct__
key in the function clause:
def some_fun(%__struct__: _module}), do: ...
But this just plain looks dirty.
is_struct/1
Starting in Erlang/OTP 21, a couple of new guards were introduced that we can use to define our own:
defguard is_struct(term) when is_map(term) and :erlang.is_map_key(:__struct__, term)
defguard is_struct(term, module) when is_struct(term) and :erlang.map_get(:__struct__, term) == module
Now you can use them as function guards:
def do_something(account) when is_struct(account, User) do
# Do something with %User{} structs
end
def do_something(struct) when is_struct(struct) do
# Do something with random structs
end
def do_something(map) when is_map(map) do
# Do something with maps
end
Note: These will soon be added to Elixir standard library itself. Follow the discussion in this ElixirForum thread.
Upvotes: 5
Reputation: 4485
It is possible to pattern match between struct and map like below
defmodule DifferentThings do
def do_something(arg = %_x{}) do
IO.puts "This is a struct"
end
def do_something(arg = %{}) do
IO.puts "This is a map"
end
end
Upvotes: 11
Reputation: 1929
In general to check if map is a struct:
Map.has_key?(struct, :__struct__)
For different method declarations (more general method is second):
defmodule DifferentThings do
def do_something(%{__struct__: _} = arg) do
# ...
end
def do_something(arg) when is_map(arg) do
# ...
end
end
Upvotes: 28
Reputation: 9109
You can't have separate function heads for Map vs Struct with a guard, but you can do it with pattern matching.
defmodule Guard do
def foo(%{:__struct__ => x }) do
Struct
end
def foo(x) when is_map x do
Map
end
end
Upvotes: 5
Reputation: 5812
You can check easily keys
of current map with Map.keys/1
.
For both map and struct is_map/1
will return true, but in your example:
Map.keys(%{}) will return []
and
Map.keys(struct)
will return collection of keys, eg. [:__struct__, :name, :age]
.
So you can simply use:
:__struct__ in Map.keys(struct).
If you want to have this is_struct
make it macro then.
Upvotes: 4