redplanetlabs / specter

Clojure(Script)'s missing piece
Apache License 2.0
2.51k stars 102 forks source link

filter map of maps by key (regex or substring) #309

Closed zenlor closed 3 years ago

zenlor commented 3 years ago

Hello I have a question, I want to use specter to select large JSON bodies coming from ElasticSearch's statistics. Some of the statistics I'm trying to reach have dynamic path names, at this time I'm only interested in select them.

I'll have to compose selectors from my program's dynamic configuration. I'm trying to find the best way to tackle this using specter to cleanup my current clojure code (which I found difficult to debug and share with a team).

My data would have nested paths, but this should suffice as streamlined example:

(def data
  {:foo-1 {:data :foo123}
   :foo-2 {:data :foo456}
   :foo-3 {:data :foo789}
   :bar-1 {:data :bar123}
   :bar-2 {:data :bar456}
   :bar-3 {:data :bar789}})

(s/select [s/ALL (s/if-path [s/FIRST s/NAME #(string/starts-with? % "foo")] (s/nthpath 1))]
          data)
;; => [{:data :foo123} {:data :foo456} {:data :foo789}]

(defn key-starts-with? [n]
  #(-> %
       first
       name
       (string/starts-with? n)))

(defn filter-by-path
  [data p]
  (->> data
       (filter (key-starts-with? p))
       (map second)))

(s/defdynamicnav path-starts-with? [n]
  [(s/view #(filter-by-path % n)) s/ALL])

(s/select [(path-starts-with? "foo")]
          data)
;; => [{:data :foo123} {:data :foo456} {:data :foo789}]

In this example I'm only selecting the matching maps, in the real program I'll select a number of values to run statistics on them.

My first attempt was using if-path, it still needs to select ALL and then MAP-VALS in the end. In my second attempt I'm doing the same as if-path with a set of functions and a dynamic navigator.

It feels like I'm missing something important about specter.

Thank you for any insights.

nathanmarz commented 3 years ago

I would suggest doing something along the lines of:

(defn vals-with-prefix [s]
  (path ALL (selected? FIRST NAME #(string/starts-with? % s)) LAST))

(select [(vals-with-prefix "foo") <rest of path>] data)
zenlor commented 3 years ago

Thanks!

I knew I was missing something important. using path will make my code far cleaner and easier to read, understand and even compose.