specter

stephenmhopper 2024-06-02T02:19:56.774519Z

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

stephenmhopper 2024-06-02T02:32:18.977729Z

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?

nathanmarz 2024-06-02T02:39:02.070579Z

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

stephenmhopper 2024-06-02T02:41:18.623539Z

Cool. Thank you!

stephenmhopper 2024-06-05T19:48:21.493359Z

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?

stephenmhopper 2024-06-05T19:50:39.167459Z

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?

nathanmarz 2024-06-05T20:13:55.865439Z

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

nathanmarz 2024-06-05T20:13:58.530869Z

otherwise that looks right

nathanmarz 2024-06-05T20:28:10.683669Z

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

nathanmarz 2024-06-05T20:28:44.991349Z

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

stephenmhopper 2024-06-06T02:14:07.079089Z

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!