erlang

How can I replace only certain values in a map of maps, while keeping all the others?


I have a very deep map of maps with lots of values, where I want to update only a few values, while keeping all the rest. I thought that maps:merge/2 would be just the ticket, but the problem with that is that it will replace every key, all the way up to the root. I only want to update the value in the leaf. This is what maps:merge/2 does:

>maps:merge(#{a => apple, b => #{c => cherry, d => date}}, #{b => #{c => cantaloupe}}).
#{a => apple, b => #{c => cantaloupe}}).

The result I would like is #{a => apple, b => #{c => cantaloupe, d => date}}. I tried working with maps:merge_with/3, but couldn't figure it out. Is there a clever way to do this?


Solution

  • Using maps:merge_with/3 with a recursive function should be enough here.

    A quick and dirty one-liner:

    1> Fun = fun F(_K, L, R) when is_map(L) -> maps:merge_with(F, L, R); F(_K, _L, R) -> R end.
    #Fun<erl_eval.17.39164016>
    2> Fun(nil, #{a => apple, b => #{c => cherry, d => date}}, #{b => #{c => cantaloupe}}).
    #{b => #{c => cantaloupe,d => date},a => apple}
    

    A more readable version:

    deep_merge(Left, Right) -> maps:merge_with(fun
        (_, L, R) when is_map(L) -> deep_merge(L, R); 
        (_, _, R) -> R end
    , Left, Right).