Reputation: 15156
I have a structure
s = [
a: %Bla{
b: "c"
}
]
I want to take c
value from it. I'm trying to do
get_in(s, [:a, :b])
But it's not designed to take value from the struct. Is there any analogue which allows me to fetch c
from the list with nested struct?
Upvotes: 10
Views: 9989
Reputation: 11
As of Elixir 1.17, Kernel.get_in/1 supports traversing structs. So this question could now be answered simply with get_in(s[:a].b)
. Alternatively, in the case where the outer keyword list is actually a struct, you can do get_in(s.a.b)
.
Upvotes: 1
Reputation: 5983
If you're worried about Access.key/2 not working with non-maps, it's fairly simple to write your own implementation that doesn't throw a fit if the data isn't a map.
def key(key) do
# example only includes :get, not :get_and_update
fn
:get, %{^key => value}, next -> next.(value)
:get, _data, next -> next.(nil)
end
end
Upvotes: 0
Reputation: 61
This is my response which is more forgiving of non-map values encountered, such as a mid-traversal value being nil a string or something else Map.get()
will complain about
This is essentially a more-forgiving version of Kernel.get_in/2
. If any key is missing, or if mid-traversal it gets a non-mappy thing, it will return nil.
@spec key_getter(map(), list(atom() | String.t())) :: any() | nil
def key_getter(nil, _), do: nil
def key_getter(map_or_struct, []), do: map_or_struct
def key_getter(map_or_struct, _) when not is_map(map_or_struct), do: nil
def key_getter(map_or_struct, [next_key | keys]),
do: key_getter(Map.get(map_or_struct, next_key, nil), keys)
Upvotes: 1
Reputation: 222198
As documented, get_in
does not work with structs by default:
The Access syntax (foo[bar]) cannot be used to access fields in structs, since structs do not implement the Access behaviour by default. It is also design decision: the dynamic access lookup is meant to be used for dynamic key-value structures, like maps and keywords, and not by static ones like structs.
There are two ways to achieve what you want:
Implement Access
behaviour for your struct.
Use Access.key(:foo)
instead of :foo
.
I would use (2):
iex(1)> defmodule Bla do
...(1)> defstruct [:b]
...(1)> end
iex(2)> s = [a: %Bla{b: "c"}]
[a: %Bla{b: "c"}]
iex(3)> get_in(s, [:a, Access.key(:b)])
"c"
Upvotes: 17
Reputation: 9851
Here is my version of try
function to return values from both maps and structs:
def try(map, keys) do
Enum.reduce(keys, map, fn key, acc ->
if acc, do: Map.get(acc, key)
end)
end
Upvotes: 4