Why Not a Function #12: deep-merge (with)
(defn deep-merge
[m1 m2 & {:keys [with]}]
(into {}
(map (fn [k]
(let [v1 (get m1 k)
v2 (get m2 k)]
[k (cond
(and (map? v1) (map? v2)) (deep-merge v1 v2 :with with)
(and with (contains? m1 k) (contains? m2 k)) (with v1 v2)
(contains? m2 k) v2
:else v1)]))
(set/union (keys m1) (keys m2)))))
deep-merge
was already mentioned on this blog when we discovered a bug in one of the previous versions of it. We showed a fixed version back then as well, but the version above is even more up to date as we have added a new functionality.
The basic deep-merge
works just as standard merge
but is “deep” meaning that if maps have submaps it will merge these submaps rather than replacing them.
(merge
{:keyboard {:color "black", :plug "usb"}, :mouse {:make "logitech"}}
{:keyboard {:plug "ps/2"}})
=> {:keyboard {:plug "ps/2"}, :mouse {:make "logitech"}}
(deep-merge
{:keyboard {:color "black", :plug "usb"}, :mouse {:make "logitech"}}
{:keyboard {:plug "ps/2"}})
=> {:keyboard {:plug "ps/2", :color "black"}, :mouse {:make "logitech"}}
We have replaced :plug
and retained :color
. What if we want to keep both :plug
values? We can use the :with
option which works similarly to Clojure’s merge-with
.
(deep-merge
{:keyboard {:color "black", :plug "usb"}, :mouse {:make "logitech"}}
{:keyboard {:plug "ps/2"}}
:with (fn [a b] (str a "; " b)))
=> {:keyboard {:plug "usb; ps/2", :color "black"}, :mouse {:make "logitech"}}
Written on March 26, 2020