Search code examples
clojurespecter

clojure find arbitrarily nested key


Is there an easy way in Clojure (maybe using specter) to filter collections depending on whether the an arbitrarily nested key with a known name contains an element ?

Ex. :

(def coll [{:res [{:a [{:thekey [
                          "the value I am looking for"
                      ...
                     ]
            }
           ]}
      {:res ...}
      {:res ...}
      ]}])

Knowing that :a could have a different name, and that :thekey could be nested somewhere else. Let's say I would like to do :

 #(find-nested :thekey #{"the value I am looking for"} coll) ;; returns a vector containing the first element in coll (and maybe others)

Solution

  • use zippers. in repl:

    user> coll
    [{:res [{:a [{:thekey ["the value I am looking for"]}]} {:res 1} {:res 1}]}]
    
    user> (require '[clojure.zip :as z])
    nil
    
    user> (def cc (z/zipper coll? seq nil coll))
    #'user/cc
    
    user> (loop [x cc]
            (if (= (z/node x) :thekey)
              (z/node (z/next x))
              (recur (z/next x))))
    ["the value I am looking for"]
    

    update:

    this version is flawed, since it doesn't care about :thekey being the key in a map, or just keyword in a vector, so it would give unneeded result for coll [[:thekey [1 2 3]]]. Here is an updated version:

    (defn lookup-key [k coll]
      (let [coll-zip (z/zipper coll? #(if (map? %) (vals %) %) nil coll)]
        (loop [x coll-zip]
          (when-not (z/end? x)
            (if-let [v (-> x z/node k)] v (recur (z/next x)))))))
    

    in repl:

    user> (lookup-key :thekey coll)
    ["the value I am looking for"]
    
    user> (lookup-key :absent coll)
    nil
    

    lets say we have the same keyword somewhere in a vector in a coll:

    (def coll [{:res [:thekey
                      {:a [{:thekey ["the value I am looking for"]}]}
                      {:res 1} {:res 1}]}])
    #'user/coll
    
    user> (lookup-key :thekey coll)
    ["the value I am looking for"]
    

    which is what we need.