Skip to content

Instantly share code, notes, and snippets.

@danielpcox
Created March 11, 2015 21:21
Show Gist options
  • Save danielpcox/c70a8aa2c36766200a95 to your computer and use it in GitHub Desktop.
Save danielpcox/c70a8aa2c36766200a95 to your computer and use it in GitHub Desktop.
Simple, recursive deep-merge in Clojure.
(ns deep-merge-spec
(:require [midje.sweet :refer :all]
[util :as u]))
(fact (u/deep-merge {:one 1 :two 2}
{:one 1 :two {}})
=> {:one 1 :two {}})
(fact (u/deep-merge {:one 1 :two {:three 3 :four {:five 5}}}
{:two {:three {:test true}}})
=> {:one 1 :two {:three {:test true} :four {:five 5}}})
(fact (u/deep-merge {:one {:two {:three 3}}
:four {:five {:six 6}}}
{:one {:seven 7 :two {:three "three" :nine 9}}
:four {:eight 8 :five 5}
:ten 10})
=> {:one {:two {:three "three" :nine 9}
:seven 7}
:four {:five 5 :eight 8}
:ten 10})
(fact (u/deep-merge {:one {:two 2 :three 3}}
{:one {:four 4 :five 5}})
=> {:one {:two 2 :three 3 :four 4 :five 5}})
(fact (u/deep-merge {:one 1 :two {:three 3}}
{:one 2 :two {:three 4}}
{:one 3 :two {:three 5}}
{:one 4 :two {:three 6}})
=> {:one 4 :two {:three 6}})
(ns util)
(defn deep-merge [v & vs]
(letfn [(rec-merge [v1 v2]
(if (and (map? v1) (map? v2))
(merge-with deep-merge v1 v2)
v2))]
(when (some identity vs)
(reduce #(rec-merge %1 %2) v vs))))
@fl00r
Copy link

fl00r commented Jan 10, 2020

@andrewboltachev

but

(merge {:a 1} {:a nil})
{:a nil}

In deep merge I would prefer this semantic.

But that is of course a matter of taste.

And as @dijonkitchen mentioned

After using deep-merge for a bit, I think I know why it doesn't already exist. It encourages difficult to understand nested maps when flat maps are simpler/better. Perhaps its absence speaks to it as a code smell.

Of course you can modify the code to fit your needs.

@JasonStiefel
Copy link

JasonStiefel commented Sep 25, 2020

@juman123
Copy link

juman123 commented Jan 4, 2024

Here's one more version.

This one is a lot less concise, but potentially easier to understand. It might also perform better than some other versions, since I'm using loops and recurs.

(defn merge-entries [value-fn into-map entries]
  (loop [result    into-map
         remaining (seq entries)]
    (if-not remaining
      result
      (let [entry   (first remaining)
            k       (key entry)
            new-val (val entry)
            cur-val (get result k)]
        (recur (if (nil? cur-val)
                 (assoc result k new-val)
                 (assoc result k (value-fn cur-val new-val)))
               (next remaining))))))


(defn merge-maps [value-fn & maps]
  (loop [results   nil
         remaining (seq maps)]
    (if-not remaining
      results
      (recur (merge-entries value-fn (or results {}) (-> remaining first seq))
             (next remaining)))))


(defn deep-merge [& maps]
  (let [value-fn (fn [v1 v2]
                   (if-not (and (map? v1) (map? v2))
                     v2
                     (deep-merge v1 v2)))]
    (apply merge-maps value-fn maps)))


(deep-merge {:m {:a 1 :b 2}} {:m nil} {:m {:a 2 :c 3}})
(deep-merge {:m {:a 1 :b 2}} {:m {}} {:m {:a 2 :c 3}})
(deep-merge {:a {:b true}} {:a {:b false}} {:a {:b nil}})
(deep-merge {:a 1} nil)
(deep-merge {:a {:b true}} {:a {:b false}} {:a {:b nil}})
(deep-merge {:one 1 :two {:three 3}}
            {:one 2 :two {:three 4}}
            {:one 3 :two {:three 5}}
            {:one 4 :two {:three 6}})

{:m {:a 2, :c 3}}
{:m {:a 2, :b 2, :c 3}}
{:a {:b nil}}
{:a 1}
{:a {:b nil}}
{:one 4, :two {:three 6}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment