Search code examples
clojureclojurescriptnoir

'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!


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.