TIL: Using functions as keys with update_in in Elixir
If you aren't already aware, Elixir has some very handy convenience helpers for accessing and updating nested values: get_in
, put_in
and update_in
. For example, you can use get_in
to access a value deeply nested in a JSON response by passing in an array of keys:
json = %{
"data" => %{
"level_1" => %{
"level_2" => %{
"value" => 1
}
}
}
}
get_in(json, ["data", "level_1", "level_2", "value"])
# 1
Or use put_in
to set a nested value:
# Same JSON
put_in(json, ["data", "level_1", "level_2", "value"], 2)
# Sets the value and returns a new map
%{
"data" => %{
"level_1" => %{
"level_2" => %{
"value" => 2
}
}
}
}
Similarly, you can use update_in
to update a deeply nested value:
# Same JSON as before
update_in(json, ["data", "level_1", "level_2", "value"], &(&1 + 1)
# Updates the value and returns a new map
%{
"data" => %{
"level_1" => %{
"level_2" => %{
"value" => 2
}
}
}
}
These helpers are fantastic and gets rid of a lot of boilerplate code. However, there may be a case where you're given an array of items and you want to update a specific one. You can't do that just by passing in the index value of the array you want to update. Luckily, you can use a function as a key.
Let's define our helper function to use as a key. You might have to know ahead of time what operation you're expecting to happen to your structure.
# Helper function to access an item in an array
item_at_index = fn index ->
fn :get_and_update, list, next_op ->
item = Enum.at(list, index)
# Update the item with the expected change
{_, updated_item} = next_op.(item)
# Must return a tuple of the item in question and the updated version of the container. In this case, the updated list
{item, List.replace_at(list, index, updated_item)}
end
end
Now let's use that function as a key to update a map.
json = %{
"data" => %{
"items" => [
%{
"value" => "foo"
},
%{
"value" => "bar"
},
%{
"value" => "foobar" # I want this to be capitalized
}
]
}
}
# Update the map
update_in(json, ["data", "items", item_at_index.(2), "value"], &String.upcase(&1))
# Our updated map
%{
"data" => %{
"items" => [
%{
"value" => "foo"
},
%{
"value" => "bar"
},
%{
"value" => "FOOBAR"
}
]
}
}
Wrap-up
Elixir gives us the tools to write amazing code and there's always plenty to explore. Take a look at at the docs for update_in , get_in, and put_in to learn more.