clojure-spec

Santiago 2026-04-06T16:05:55.524009Z

I'm trying to write specs for a map and I would prefer to not write explicit (s/def ...) for each keyword of the map. Therefore, looking at the docs it looks like I can use map-of . This works if the map is homogeneous, but my map has groups of keywords which are homogenous amongst them, while different from other groups. I tried merge but that does not work. Here's an example and the error message from spec:

(def filter-screens-set
  #{:pca/m1cs
    :pca/m1cr})

(def nav-screens-set
  #{:pca/main-screen})

(s/def ::filter-screens
  (s/map-of filter-screens-set ::filter-screen))

(s/def ::nav-screens
  (s/map-of nav-screens-set ::nav-screen))

(s/def ::all-screens (s/merge ::nav-screens ::filter-screens))

(s/explain
  ::all-screens
  {:pca/main-screen {:title "Prostate cancer"
                     :children [{:title "Castration sensitive PCA"
                                 :group "Metastatic disease"
                                 :screen-id :filters
                                 :target :pca/m1cs}]}
   :pca/m1cs {:title "M1 CS prostate cancer"
              :tags #{:pca :m1 :castration-sens}
              :filters [{:group-label "CHAARTED level"
                         :tags #{:CHAARTED-low :CHAARTED-high}}]}})

; eval (root-form): (s/explain ::all-screens {:pca/main-screen {...
; (out) :pca/m1cs - failed: nav-screens-set in: [:pca/m1cs 0] at: [0] spec: :test/nav-screens
; (out) {:title "M1 CS prostate cancer", :tags #{:pca :castration-sens :m1}, :filters [{:group-label "CHAARTED level", :tags #{:CHAARTED-high :CHAARTED-low}}]} - failed: (contains? % :children) in: [:pca/m1cs 1] at: [1] spec: :test/nav-screen
; (out) :pca/main-screen - failed: filter-screens-set in: [:pca/main-screen 0] at: [0] spec: :test/filter-screens
; (out) {:title "Prostate cancer", :children [{:title "Castration sensitive PCA", :group "Metastatic disease", :screen-id :filters, :target :pca/m1cs}]} - failed: (contains? % :tags) in: [:pca/main-screen 1] at: [1] spec: :test/filter-screen
; (out) {:title "Prostate cancer", :children [{:title "Castration sensitive PCA", :group "Metastatic disease", :screen-id :filters, :target :pca/m1cs}]} - failed: (contains? % :filters) in: [:pca/main-screen 1] at: [1] spec: :test/filter-screen
My understanding is that merge is working like an and which means it always fails one of the specs? Is there a way to do this, or must I be explicit with keys?

😮 1
✅ 1
Alex Miller (Clojure team) 2026-04-06T16:36:24.094439Z

I don't think what you're trying will work - you are defining a spec where keys have to pass both specs and they are not compatible

Santiago 2026-04-06T17:45:59.291219Z

Right. Is the only option here to split the map and check the spec conforms on each split? I dont see a way to define a spec that conforms a map like I describe without defining each key with keys or modifying the structure eg nesting

Alex Miller (Clojure team) 2026-04-06T18:02:08.188809Z

you could have each map spec spec the keys from the other set as any? - those specs could then be merged, but I'm not sure that's any better

Alex Miller (Clojure team) 2026-04-06T18:05:55.805359Z

it is also possible to spec a map as a collection of kvs, but this is also fairly tricky. I used a similar technique here https://www.cognitect.com/blog/2017/1/3/spec-destructuring - see the "collection of tuple forms" part with s/every of s/or

Santiago 2026-04-07T08:35:31.007889Z

thanks Alex! I've concluded I'm going against the system here so instead I added some nested keys to the map. This way I can still use map-of at the cost of a little nesting.