Search code examples
clojurespecter

Trim unnecessary entries from deeply nested data structure using specter


I'm looking to use Clojure Specter to simplify a deeply nested datastructure. I want to remove:

  • any entries with nil values
  • any entries with empty string values
  • any entries with empty map values
  • any entries with empty sequential values
  • any entries with maps/sequential values that are empty after removing the above cases.

Something like this:

(do-something
    {:a {:aa 1}                                               
       :b {:ba -1                                               
           :bb 2                                                
           :bc nil
           :bd ""
           :be []
           :bf {}
           :bg {:ga nil}
           :bh [nil]
           :bi [{}]
           :bj [{:ja nil}]}
       :c nil
       :d ""
       :e []
       :f {}
       :g {:ga nil}
       :h [nil]
       :i [{}]
       :j [{:ja nil}]})
    =>
    {:a {:aa 1} 
         :b {:ba -1 
             :bb 2}}

I have something in vanilla Clojure:

(defn prunable?
  [v]
  (if (sequential? v)
    (keep identity v)
    (or (nil? v) (#{"" [] {}} v))))

(defn- remove-nil-values
  [ticket]
  (clojure.walk/postwalk
    (fn [el]
      (if (map? el)
        (let [m (into {} (remove (comp prunable? second) el))]
          (when (seq m)
            m))
        el))
    ticket))

I think I need some sort of recursive-path but I'm not getting anywhere fast. Help much appreciated.


Solution

  • Thanks to nathanmarz on the Clojurians slack channel:

    (def COMPACTED-VALS-PATH
      (recursive-path [] p
                      (continue-then-stay
                        (cond-path
                          map? [(compact MAP-VALS) p]
                          vector? [(compact ALL) p]))))
    
    (defn- compact-data
      [m]
      (setval [MAP-VALS COMPACTED-VALS-PATH #(or (nil? %) (= "" %))] NONE m))