0

I'm looking for a function to achieve the following example result:

{"foo1"      "baz"
 "foo2.bar"  "baz"
 "foo2.bar2" "baz"
 "foo3_bar"  "baz"}
=>
{:foo1 "baz"
 :foo2 {:bar  "baz"
        :bar2 "baz"}
 :foo3 {:bar  "baz"}}

As one can see, it's a bit different from a classic deep-merge as the keys have to be keywordized first in a way that dot- and underscore postfixes are converted to hash maps (instead of the usual #[_\.]=> -).

3
  • 1
    I think you expect the last entry to be [:foo2 {:bar "baz"}] right? Commented Oct 27, 2013 at 13:39
  • Yes, (almost) correct. I fixed it. Commented Oct 27, 2013 at 17:08
  • Unfortunately having two different entries under :foo1 is not possible . Commented Oct 27, 2013 at 17:20

3 Answers 3

2
(defn parse-keys-and-merge
  [hm]
  (reduce-kv (fn [hm k v]
               (assoc-in hm (map keyword (clojure.string/split k #"[\._]"))
                         (if (map? v)
                           (parse-keys-and-merge v)
                           v)))
             {} hm))

This does not work for your hash-map because your hash-map does not clarify whether the entry for :foo should be "baz" or {:bar "baz", :bar2 "baz"}. With a fixed hash-map it works:

(parse-keys-and-merge {"foo2_bar" "baz", "foo.bar2" "baz", "foo.bar" "baz"})
;; {:foo {:bar "baz", :bar2 "baz"}, :foo2 {:bar "baz"}}
Sign up to request clarification or add additional context in comments.

5 Comments

You are correct. I went ahead of myself where I want to apply this along the (merge) function. I updated the input and resutl hash map to result my need more clearly.
Where is (reduce-kv) declared?
I just tried the (parse-keys-and-merge) and it fails in one specific case: {"foo.bar" "hello", "foo.bar.baz" "world"}
Yes, because you can not simultaneously have a string and a hash-map as an entry for foo.bar.
0

With inspiration from @lgrapenthin I came up for this solution. It is on the upside short and concise and on the downside expensive (which is not to bad for my use case) and the overwriting strategy is determined by Clojure's hash map sorting (aka for user's undetermined):

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

(defn- str-keys-to-map [[k v]]
  (let [ks (map keyword (filter not-empty (string/split k #"[\._]")))]
    (when-not (empty? ks) (assoc-in {} ks v))))

(defn deep-keywordize-keys [m]
  (->> m (map str-keys-to-map) (apply deep-merge)))

Comments

-1

You could use a function like this one. Please note that it could be optimized to do tail recursion.

        (defn deep-hashmap-merge
          [ m ]
          (let
            [
            tget (fn [r k d]
                    (let
                      [ t (get r k d)]
                      (if (associative? t) t d))) 
            get-keylist-value (fn [r [k & ks] kv]
                              (if (nil? ks)
                                (assoc r k kv)
                                (assoc r k (get-keylist-value (tget r k {}) ks kv))))     
            ]
              (reduce #(get-keylist-value %1 (map keyword (clojure.string/split (first %2) #"[_\.]")) ( second %2)) {} m)
            )
          )

And the output would then be :

              user=> (deep-hashmap-merge 
              #_=> {"foo"      "baz"
              #_=>  "foo.bar"  "baz"
              #_=>  "foo.bar2" "baz"
              #_=>  "foo2_bar" "baz"})

              {:foo {:bar "baz", :bar2 "baz"}, :foo2 {:bar "baz"}}

1 Comment

//added keyword (keys were strings)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.