Fork me on GitHub
#clojure-spec
<
2022-11-06
>
eighttrigrams11:11:38

Hey, I've got a data structure (which I cannot change) like

[{:identifier "abc" :other-key-1 "abc"}
 {:identifier "SELF" :content {}}
 {:identifier "def" :other-key-2 []}]
I would like to do the following: 1. Determine if there is exactly one item in the vector of maps which has the :identifier "SELF". 2. For that item, determine if there exists the key :content 3. Make sure its value is a map (whose properties I also would like to spec) Can anyone think of a good way of doing this in spec or give me pointers of how to think about this?

walterl12:11:53

(require '[clojure.spec.alpha :as s])

(def d [{:identifier "abc" :other-key-1 "abc"}
        {:identifier "SELF" :content {}}
        {:identifier "def" :other-key-2 []}])

(defn- self-id? [{:keys [identifier]}] (= "SELF" identifier))

(s/def :self/identifier #{"SELF"})
(s/def :self/content (s/keys)) ; TBD
(s/def ::self (s/keys :req-un [:self/identifier :self/content]))
(s/def ::not-self (s/or :!m (complement map?) :!self (complement self-id?)))

(s/explain-data ::self (first (filter self-id? d)))

(s/def ::d (s/and (s/coll-of map?)
                  (s/cat :before (s/* ::not-self)
                         :self ::self
                         :after (s/* ::not-self))))

(s/explain-data ::d d)
(s/conform ::d d)
; {:before [[:!self {:identifier "abc", :other-key-1 "abc"}]],
;  :self {:identifier "SELF", :content {}},
;  :after [[:!self {:identifier "def", :other-key-2 []}]]}

eighttrigrams14:11:08

This is fantastic! Thank you so much, @UJY23QLS1

👍 1
walterl14:11:43

> "...pointers of how to think about this" Thing of it like regex: you want to match 0 or more non-self maps, followed by a self map, followed by 0 or more non-self maps. (An s/not spec would've been handy here.)

eighttrigrams14:11:07

Yeah, like a regex. It totally makes sense. The docs even stated this, but just didn't come up with this in the moment.

eighttrigrams14:11:54

> An s/not spec would've been handy here. :thinking_face: Interesting. I see, for the :not-self ...

walterl14:11:08

Yeah, I would've liked to do (s/not ::self) rather than having to define a negation of ::self manually, because now they could drift out of sync.

eighttrigrams14:11:24

Yes. I have an intuition that it should generally be possible to make such a thing. One would need to account for the different, uhm, don't know the name, the different things like s/keys , s/cat and so on. The negation of the s/keys was an s/or with complement. Not in the mood to solve that puzzle right now, but certainly an interesting challenge 😀