Fork me on GitHub
#specter
<
2024-06-02
>
stephenmhopper02:06:56

That's close to what I want. However, there's sometimes maps nested inside of vectors / list and I need to handle those too. So imagine data looks like this:

(def data
  {:AThing {:AAThing [{:AABAThing 1} {:AABBThing 2} {:AABCThing 3}]
            :A2Thing {:AAAThing "x"}}})
I'm not concerned about records or things like that for this use case. It's just all plain Clojure maps

stephenmhopper02:06:18

This seems to have the desired effect:

(def ALL-MAPS
  (specter/recursive-path [] p
    (specter/if-path map?
      (specter/continue-then-stay specter/MAP-VALS p)
      (specter/if-path (some-fn seq? vector? set? list?)
        specter/ALL)
      )))

(def data
  {:AThing {:AAThing [{:AABAThing 1} {:AABBThing 2} {:AABCThing 3}]
            :A2Thing {:AAAThing "x"}}})

(specter/transform [ALL-MAPS specter/MAP-KEYS] csk/->snake_case data)
Any reasons not to do it this way?

nathanmarz02:06:02

it would be more elegant with cond-path, but you have the right idea

stephenmhopper02:06:18

Cool. Thank you!

stephenmhopper19:06:21

I updated to use cond-path, but it looks like the transformer isn't fully recursive.

(def ALL-MAPS
  (specter/recursive-path [] p
    (specter/cond-path
      map? (specter/continue-then-stay specter/MAP-VALS p)
      (some-fn seq? vector? set? list?) specter/ALL)))

(def data
  {:AThing {:AAThing [{:AABAThing 1} {:AABBThing 2} {:AABCThing 3 :DeeplyNestedThing [{:ABC 1} {:ABC 2}]}]
            :A2Thing {:AAAThing "x"}}})

(specter/transform [ALL-MAPS specter/MAP-KEYS] csk/->snake_case data)
Result is this:
{:a_thing {:aa_thing [{:aaba_thing 1} {:aabb_thing 2} {:aabc_thing 3, :deeply_nested_thing [{:ABC 1} {:ABC 2}]}],
           :a_2_thing {:aaa_thing "x"}}}
What do I need to change for the maps in deeply_nested_thing to be selected too?

stephenmhopper19:06:39

This seems to work:

(def ALL-MAPS
  (specter/recursive-path [] p
    (specter/cond-path
      map? (specter/continue-then-stay specter/MAP-VALS p)
      (some-fn seq? vector? set? list?) [specter/ALL p])))
(note the change to [specter/ALL p] ) Is that the right way to do it? Why did the other way mostly work?

nathanmarz20:06:55

I think you can change the some-fn part to just sequential?

nathanmarz20:06:58

otherwise that looks right

nathanmarz20:06:10

or I suppose just replace seq?, vector?, and list? with sequential?

nathanmarz20:06:44

I would also wrap the some-fn part in pred to make it higher performance, since otherwise Rama needs to determine how to convert the function to a navigator at runtime

stephenmhopper02:06:07

Originally, I was going to use sequential?, but I realized it didn't match for set. So I tried seqable? but that had the unintended consequence of also matching strings. Using sequential? with set? works well. Final product looks like this:

(def ALL-MAPS
  (specter/recursive-path [] p
    (specter/cond-path
      map? (specter/continue-then-stay specter/MAP-VALS p)
      (specter/pred (some-fn sequential? set?)) [specter/ALL p])))
Thanks for the help!