specter

dgr 2025-06-18T00:19:36.821109Z

I’m trying to use Specter to convert some dates in string form to java.time.LocalDate objects in a deep map/vector hierarchy. I have a recursive path that correctly visits all the leaves of the tree:

(def TREE-VALS
  (s/recursive-path [] p-vals
                    (s/cond-path vector? [s/ALL p-vals]
                                 map? [s/MAP-VALS p-vals]
                                 string? s/STAY)))
and I’m trying to do this:
(s/transform [TREE-VALS #"\d\d\d\d-\d\d-\d\d"] ld/parse my-data-structure)
where “ld” is the cljc.java-time.LocalDate library and ld/parse is effectively java.time.LocalDate/parse. But I get this error:
Execution error (ClassCastException) at com.rpl.specter$fn$reify__16117/transform_STAR_ (specter.cljc:1136).
class java.time.LocalDate cannot be cast to class java.lang.String (java.time.LocalDate and java.lang.String are in module java.base of loader 'bootstrap')
It seems like once it navigates to a string, it doesn’t want to be able to transform that to a LocalDate. If my transformation function returns a string (e.g., #(str % "-blah")), it works as expected. So, what am I doing wrong? Specter can convert a visited object from one type to another, right?!?!

nathanmarz 2025-06-18T00:26:39.634229Z

using a regex implicitly uses regex-nav, which calls (clojure.string/replace structure re next-fn)

nathanmarz 2025-06-18T00:27:03.309399Z

so using a regex navigates to the matches within that string

nathanmarz 2025-06-18T00:27:13.456829Z

whereas you're expecting it to act like a predicate to filter

nathanmarz 2025-06-18T00:28:30.234059Z

you should use something like (fn [s] (re-matches #"\d\d\d\d-\d\d-\d\d" s))

dgr 2025-06-18T00:29:41.999199Z

Ah, OK, gotcha. Makes sense. Use that function (or something like it) as a path predicate or do the match in the transformation function (e.g., if match, then parse the date, otherwise return the same string)?

nathanmarz 2025-06-18T00:29:58.687109Z

I would do that in the path

nathanmarz 2025-06-18T00:30:11.305469Z

if you do it in the transform function, then you need to return the value unchanged in the else case

nathanmarz 2025-06-18T00:30:37.355829Z

fyi, this is the part of the Specter implementation which determines how a regex in a path is interpreted https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter.cljc#L1260

dgr 2025-06-18T00:31:39.131159Z

OK, thanks Nathan. Much appreciated!

dgr 2025-06-18T00:40:18.896589Z

Another qq: Is it a lot more efficient to perform multiple transformations in a single call to multi-transform than multiple calls to transform? I have a couple of different date types I need to convert (both local dates and zoned date-times). Doing it as multiple passes is fairly straight-forward, but that seems like it would be a lot slower since I’d have to visit every node in the tree twice.

nathanmarz 2025-06-18T00:43:55.003839Z

a multi-transform would be faster

👍 1