Fork me on GitHub
#specter
<
2020-10-27
>
mathpunk18:10:10

I think I've got a use case that specter might help, but I'm brand new to it. I've got data that looks like this:

{:name "Conditional Sections E2E Workflow"
 :application {:name "Conditional Sections"
               :id "someId"}} 
There's nested maps, and any time there's an :id key in the map, I need to look up the value of :id in another map, and assoc that value in place.

mathpunk18:10:31

I'm reading about navigators now... if y'all have a pointer to the one/ones I need, I'd appreciate it

idiomancy18:10:35

usually the best way to get help around here is to give a sample input and output. Someone will invariably take up the challenge as a little brain teaser to occupy themselves while they wait for their next meeting to start and post the answer

mathpunk18:10:26

I can do that, I'll find a good one

mathpunk18:10:39

Okay, suppose there is a map called state floating about, like shown. Here's the given data, and the desired data:

{:state {"FzPIxXKk" {:name "Conditional Sections E2E Workflow"
                          :id "var"}}
      :given {:name "Conditional Sections E2E Workflow"
              :application {:name "Conditional Sections"
                            :id "FzPIxXKk"}}
      :desired {:name "Conditional Sections E2E Workflow"
                :application {:name "Conditional Sections"
                              :id "var"}}}

mathpunk18:10:23

For reference, here is the brittle "let's hope we got all the cases" code I have for doing this:

(defn return-with-id [exchange state]
  (assoc-in exchange [:required :id] (get (get state (get-in exchange [:required :id])) :id)))

(defn return-with-application [exchange state]
  (assoc-in exchange [:required :application :id] (get (get state (get-in exchange [:required :application :id])) :id)))

(defn return-with-parent [exchange state]
  (assoc-in exchange [:required :parent :id] (get (get state (get-in exchange [:required :parent :id])) :id)))

(defn return-with-target [exchange state]
  (assoc-in exchange [:required :target :id] (get (get state (get-in exchange [:required :target :id])) :id)))

(defn return [state exchange]
  (if (seq? exchange)
    (throw (Exception. "Multiple requirements needed, invalidating my assumption about our data"))
    (cond-> exchange
      (get state (get-in exchange [:required :id])) (return-with-id state)
      (get state (get-in exchange [:required :application :id])) (return-with-application state)
      (get state (get-in exchange [:required :target :id])) (return-with-target state)
      (get state (get-in exchange [:required :parent :id])) (return-with-parent state))))

mathpunk18:10:08

I could replace those copypasta functions with one function that takes a path -- but it would still be, explicit paths.

mathpunk18:10:30

Then I'd have,

(defn update-with-path [state exchange path]
  (cond-> exchange
    (get state (get-in exchange (concat [:required] path)))
    (assoc-in (concat [:required] path) (get (get state (get-in exchange (concat [:required] path))) :id))))

(defn return [state exchange]
  (if (seq? exchange)
    (throw (Exception. "Multiple requirements needed, invalidating my assumption about our data"))
    (let [update-fn (partial update-with-path state)]
      (-> exchange
          (update-fn [:id])
          (update-fn [:application :id])
          (update-fn [:target :id])
          (update-fn [:parent :id])))))

mathpunk18:10:35

And hey, that does work:

{:state {"FzPIxXKk" {:name "Conditional Sections E2E Workflow"
                     :id "var"}}
 :given {:method "POST"
         :url ""
         :required {:name "Conditional Sections E2E Workflow"
                    :application {:name "Conditional Sections"
                                  :id "FzPIxXKk"}}} ;; <-- needs updating
 :result {:method "POST"
          :url ""
          :required {:name "Conditional Sections E2E Workflow"
                     :application {:name "Conditional Sections"
                                   :id "var"}}}} ;; <-- updated

mathpunk18:10:57

but like i said, not generic, seems brittle, gotta know what paths to :id's exist in order to explicitly supply them

nathanmarz19:10:00

@mathpunk take a look at recursive-path, MAP-VALS, and stay-then-continue

nathanmarz19:10:17

you can define a navigator that goes to every nested map, and then from there do your lookup of the id

nathanmarz19:10:07

e.g. (transform [ALL-MAPS (must :id)] (fn [id] (lookup-val-for-id id)) data)

mathpunk19:10:55

great, thank you!