Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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))))
@cmal

This comment has been minimized.

Copy link

commented Jan 2, 2018

Hi, how to change deep-merge and make it work for nil?

user=> (deep-merge {:a 1} nil)
nil
user=> (merge {:a 1} nil)
{:a 1}
@cjsauer

This comment has been minimized.

Copy link

commented Jan 7, 2018

@cmal this worked for me:

(defn deep-merge [v & vs]
  (letfn [(rec-merge [v1 v2]
            (if (and (map? v1) (map? v2))
              (merge-with deep-merge v1 v2)
              v2))]
    (if (some identity vs)
      (reduce #(rec-merge %1 %2) v vs)
      v)))
@loganpowell

This comment has been minimized.

Copy link

commented Jun 21, 2018

OMG, this worked perfectly for me! They should add something like this to core! Thank you so much!

@Freezystem

This comment has been minimized.

Copy link

commented Aug 13, 2018

@cjsauer actually your script didn't work at all. You have to return (last vs) not v:

(defn deep-merge [v & vs]
  (letfn [(rec-merge [v1 v2]
            (if (and (map? v1) (map? v2))
              (merge-with deep-merge v1 v2)
              v2))]
    (if (some identity vs)
      (reduce #(rec-merge %1 %2) v vs)
      (last vs))))

e.g:

(deep-merge {:a {:b true}} {:a {:b false}} {:a {:b nil}})
; with your script: 
{:a {:b true}}
; with mine: 
{:a {:b nil}}
@dijonkitchen

This comment has been minimized.

@dijonkitchen

This comment has been minimized.

Copy link

commented Sep 13, 2018

@cjsauer actually your script didn't work at all. You have to return (last vs) not v:

(defn deep-merge [v & vs]
  (letfn [(rec-merge [v1 v2]
            (if (and (map? v1) (map? v2))
              (merge-with deep-merge v1 v2)
              v2))]
    (if (some identity vs)
      (reduce #(rec-merge %1 %2) v vs)
      (last vs))))

e.g:

(deep-merge {:a {:b true}} {:a {:b false}} {:a {:b nil}})
; with your script: 
{:a {:b true}}
; with mine: 
{:a {:b nil}}

@Freezystem you're right that your deep-merge works with nil values, but not nil maps.

Example:

(deep-merge {:a 1} nil)
nil ;; but it should be {:a 1}

@cjsauer's works for nil maps, but not nil values as you noted.

Is there a way to reconcile the two and be able to handle both nil maps and maps with nil values?

@fl00r

This comment has been minimized.

Copy link

commented Sep 20, 2018

(defn- deep-merge [& maps]
  (let [merge-fn (fn *merge* [& args]
                   (if (every? map? args)
                     (apply merge-with *merge* args)
                     (last args)))]
    (apply merge-with merge-fn maps)))
(deep-merge {:a 1} nil)
#=> {:a 1}
(deep-merge {:a {:b true}} {:a {:b false}} {:a {:b nil}})
#=> {:a {:b nil}}

or slightly more concise

(defn deep-merge [& maps]
  (apply merge-with (fn [& args]
                      (if (every? map? args)
                        (apply deep-merge args)
                        (last args)))
         maps))
@loganpowell

This comment has been minimized.

Copy link

commented Oct 10, 2018

@fl00r this was genius. It's not only more concise, but it's about 7x faster. I brought down an @ 100mb deep-merge from @ 15 minutes to @ 2 minutes.

Thank you all so much. Progress is made!

@loganpowell

This comment has been minimized.

Copy link

commented Nov 14, 2018

Courtesy of @cgrand

(defn deep-merge
  [a b]
  (if (map? a)
    (into a (for [[k v] b] [k (deep-merge (a k) v)]))
    b))
@loganpowell

This comment has been minimized.

Copy link

commented Nov 14, 2018

And @jaihindhreddy

(defn deep-merge [a b]
  (if (map? a)
    (merge-with deep-merge a b)
    b))

and variadic:

(defn deep-merge [a & maps]
  (if (map? a)
    (apply merge-with deep-merge a maps)
    (apply merge-with deep-merge maps)))
@thobbs

This comment has been minimized.

Copy link

commented Feb 22, 2019

Note that the second, variadic version of deep-merge provided here has unusual/incorrect behavior for false-y values. For example:

dev=> (deep-merge {:a nil} {:a false})
{:a nil}

This is due to merge-with checking for at least one "truthy" argument:

(defn merge-with
  "Returns a map ..."
  [f & maps]
  (when (some identity maps)
    (let [merge-entry (fn [m e]
      ...
      (reduce1 merge2 maps))))

Which produces behavior like:

dev=> (merge-with first false nil)
nil
@dijonkitchen

This comment has been minimized.

Copy link

commented Feb 25, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.