Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'jQuery' type function for manipulating clojure maps

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!

like image 343
zcaudate Avatar asked Dec 26 '22 22:12

zcaudate


2 Answers

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"
like image 121
Arthur Ulfeldt Avatar answered Jan 14 '23 04:01

Arthur Ulfeldt


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.

like image 26
Marcin Skotniczny Avatar answered Jan 14 '23 02:01

Marcin Skotniczny