Question

Is there a jQuery type function to solve the problem of walking through nested maps?

for example, if i have a configuration that looks like this:

  (def fig
    {:config
      {:example
        {:a "a"
         :b "b"
         :c "c"}
       :more
        {:a "a"
         :b "b"
         :c "c"}}})

I still haven't figured out a great way to manipulate nested persistent data structures with assoc and dissoc. However, if there was a jquery style way to manipulate maps, then I can write code like this:

  (->  fig
    ($ [:config :example :a] #(str % "a"))
    ($ [:config :b] #(str % "b")))

  Giving this output:

  {:config
    {:example
      {:a "aa"
       :b "bb"
       :c "c"}
     :more
      {:a "a"
       :b "bb"
       :c "c"}}}

And something like this for selectors:

($ fig [:config :example :a])
  ;=> "a"

($ fig [:config :b])
  ;=> {[:config :example :b] "b", 
  ;    [:config :more :b] "b"}

So in essence, I'm looking for an implementation of jayq for manipulation of clojure objects instead of html doms.

Thanks in advance!

Was it helpful?

Solution

First of all, you should check out Enlive.

Otherwise: if you want to do what jQuery does (of course very simplified) - as opposed to just calling update-in:

Select:

(defn clj-query-select [obj path]
  (if (empty? path)
    (list obj)
    (when (map? obj)
      (apply concat
        (remove nil? 
          (for [[key value] obj]
            (clj-query-select
              value 
              (if (= key (first path)) (rest path) path))))))))

For call:

(clj-query-select {:a {:b 1} :b 2} [:b])

it should yield:

(1 2)

Update/replace:

(defn clj-query-update [obj path fn]
  (if (empty? path)
    (fn obj)
    (if (map? obj)
      (into obj
        (remove nil?
          (for [[key value] obj]
            (let [res (clj-query-update 
                        value 
                        (if (= key (first path)) (rest path) path)
                        fn)]
          (when (not= res value) [key res])))))
      obj)))

For call:

(clj-query-update {:c {:a {:b 1} :b 2}} [:c :b] #(* % 2))

it should yield:

{:c {:a {:b 2} :b 4}}

I didn't test it thoroughly though.

OTHER TIPS

update-in is a great function for updating nested maps.

user> (def data {:config
{:example  {:a "a" :b "b" :c "c"}}
 :more {:a "a" :b "b" :c "c"}})

user> (pprint (update-in data [:config :example] assoc :d 4))

{:config {:example {:a "a", :c "c", :b "b", :d 4}},
 :more {:a "a", :c "c", :b "b"}}

assoc-in may be a little closer to what you want

user> (pprint (assoc-in data [:config :example :d] 4))
{:config {:example {:a "a", :c "c", :b "b", :d 4}},
 :more {:a "a", :c "c", :b "b"}}

for reading values without changing them you can use the fact that keywords look themselves up in maps to write an even more compact form than the jquery form

user> (-> data :config :example :a)
"a"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top