Packages

set and get values from nested maps

Current section

Files

Jump to
nested src nested.erl
Raw

src/nested.erl

-module (nested).
-export([
is_key/2,
put/3,
get/2,
get/3,
update/3,
remove/2,
keys/2,
append/3
]).
is_key([Key], Map) ->
maps:is_key(Key, Map);
is_key([Key|PathRest], Map) ->
case Map of
#{Key := SubMap} -> is_key(PathRest, SubMap);
_ -> false
end.
get([Key|PathRest], Map) ->
get(PathRest, maps:get(Key, Map));
get([], Value) ->
Value.
get([Key|PathRest], Map, Default) ->
case maps:get(Key, Map, {?MODULE, Default}) of
{?MODULE, Default} ->
Default;
NestedMap ->
get(PathRest, NestedMap, Default)
end;
get([], Value, _) ->
Value.
update(Path, ValueOrFun, Map) ->
try updatef_internal(Path, ValueOrFun, Map)
catch
error:{error, {no_map, PathRest, Element}} ->
PathLength = length(Path) - length(PathRest),
PathToThrow = lists:sublist(Path, PathLength),
erlang:error({no_map, PathToThrow, Element})
end.
updatef_internal([Key|PathRest], ValueOrFun, Map) when is_map(Map) ->
maps:update(Key, updatef_internal(PathRest, ValueOrFun, maps:get(Key, Map)), Map);
updatef_internal([], Fun, OldValue) when is_function(Fun) ->
Fun(OldValue);
updatef_internal([], Value, _) ->
Value;
updatef_internal(Path, _, Element) ->
erlang:error({error, {no_map, Path, Element}}).
put([Key|PathRest], Value, Map) ->
SubMap =
case maps:is_key(Key, Map) andalso is_map(maps:get(Key, Map)) of
true -> maps:get(Key, Map);
false -> #{}
end,
maps:put(Key, put(PathRest, Value, SubMap), Map);
put([], Value, _) ->
Value.
remove([], _) ->
throw({bad_path, []});
remove([LastKey], Map) ->
maps:remove(LastKey, Map);
remove([Key|PathRest], Map) ->
case maps:is_key(Key, Map) of
true ->
maps:put(Key, remove(PathRest, maps:get(Key, Map)), Map);
false ->
Map
end.
keys([Key|PathRest], Map) ->
keys(PathRest, maps:get(Key, Map));
keys([], Map) ->
maps:keys(Map).
append(Path, Value, Map) ->
AppendFun =
fun(List) when is_list(List) ->
List ++ [Value];
(_) ->
error(no_list)
end,
update(Path, AppendFun, Map).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
is_key_test() ->
?assertEqual(false, is_key([fnord, foo, bar], #{})),
?assertEqual(true, is_key([fnord], #{fnord => 23})),
?assertEqual(true, is_key([three, two, one], test_map())),
?assertEqual(false, is_key([three, two, seven], test_map())).
get_test() ->
?assertEqual(test_map(), get([], test_map())),
?assertEqual(3, get([three_side], test_map())),
?assertEqual(2, get([three, two_side], test_map())),
?assertEqual( #{one => target, one_side => 1}, get([three, two], test_map())),
?assertEqual(target, get([three, two, one], test_map())).
get_fails_test() ->
?assertException(error, {badkey,unknown}, get([unknown], test_map())),
?assertException(error, {badkey,unknown}, get([three, unknown], test_map())),
?assertException(error, {badmap,target}, get([three, two, one, unknown], test_map())).
get_with_default_test() ->
?assertEqual(test_map(), get([], test_map(), default)),
?assertEqual(3, get([three_side], test_map(), default)),
?assertEqual(2, get([three, two_side], test_map(), default)),
?assertEqual( #{one => target, one_side => 1}, get([three, two], test_map(), default)),
?assertEqual(target, get([three, two, one], test_map(), default)),
?assertEqual(default, get([unknown], test_map(), default)),
?assertEqual(default, get([three, unknown], test_map(), default)).
get_with_default_fails_test() ->
?assertException(error, {badmap,target}, get([three, two, one, unknown], test_map(), default)).
update_test() ->
?assertEqual(3, update([], 3, test_map())),
?assertEqual(#{three => 3, three_side => 3}, update([three], 3, test_map())),
?assertEqual(
#{three => #{two => 2, two_side => 2}, three_side => 3},
update([three, two], 2, test_map())
),
?assertEqual(
#{three => #{two => #{one => target, one_side => 11}, two_side => 2}, three_side => 3},
update([three, two, one_side], fun(E) -> E + 10 end, test_map())
).
update_fails_test() ->
?assertException(error, {badkey, unknown}, update([unknown], 1, test_map())),
?assertException(error, {badkey, unknown}, update([three, unknown], 1, test_map())),
?assertException(error, {no_map, [foo,bar], []}, update([foo, bar, buz], 1, #{foo => #{bar => []}})).
put_test() ->
?assertEqual(3, put([], 3, test_map())),
?assertEqual(#{three => 3, three_side => 3}, put([three], 3, test_map())),
?assertEqual(#{three => #{two => 2, two_side => 2}, three_side => 3}, put([three, two], 2, test_map())),
?assertEqual(
#{unknown => 1, three => #{two => #{one => target, one_side => 1}, two_side => 2}, three_side => 3},
put([unknown], 1, test_map())
),
?assertEqual(
#{three => #{two => #{one => target, one_side => 1, eleven => #{twelve => 12}}, two_side => 2}, three_side => 3},
put([three, two, eleven, twelve], 12, test_map())
),
?assertEqual(
#{three => #{two => #{one => #{minus_one => -1}, one_side => 1}, two_side => 2}, three_side => 3},
put([three, two, one, minus_one], -1, test_map())
).
remove_test() ->
?assertEqual(
#{three => #{two_side => 2}, three_side => 3},
remove([three, two], test_map())
),
?assertEqual(
#{three => #{two => #{one_side => 1}, two_side => 2}, three_side => 3},
remove([three, two, one], test_map())
),
?assertEqual(
test_map(),
remove([unknown, path], test_map())
),
?assertEqual(
test_map(),
remove([three, unknown_key], test_map())
).
remove_fail_test() ->
?assertException(throw, {bad_path, []}, remove([], test_map())).
keys_test() ->
?assertEqual(
[three, three_side],
keys([], test_map())
),
?assertEqual(
[one, one_side],
keys([three, two], test_map())
).
append_test() ->
TestMap = #{outer => #{list => [1], hash => #{}}},
?assertEqual(
#{outer => #{list => [1, 2], hash => #{}}},
append([outer, list], 2, TestMap)
).
append_fail_test() ->
TestMap = #{outer => #{list => [1], hash => #{}}},
?assertException(
error, no_list,
append([outer, hash], 2, TestMap)
).
test_map() ->
L1 = #{one => target, one_side => 1},
L2 = #{two => L1, two_side => 2},
#{three => L2, three_side => 3}.
-endif.