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

consider the following data:

(def data {:names [["Anna"      :lucky]
                   ["Peter"]
                   ["Jon"       :lucky]
                   ["Andre"     :lucky]]})

It's a hash-map of with one key :names having a value of a vector of vectors. The inner vectors must contain a string as the first element and can optionally contain a the keyword :lucky as the second element.

The previous two sentences should be described with clojure.spec- Let's start with the items in the vector:

(s/def ::item (s/cat :name string? :lucky (s/? #(= :lucky %))))

(s/conform ::item ["Tom"])
;; {:name "Tom"}
(s/conform ::item ["Tom" :lucky])
;; {:name "Tom", :lucky :lucky}
(s/conform ::item ["Tom" :sad])
;; :clojure.spec/invalid

This works. However, if there is only one option. Wouldn't a parsed result look be better like this:

`{:name "Tom", :lucky true}` or `{:name "Tom", :lucky false}`

Can this be done in clojure.spec?

With this, one can carry on:

(s/def ::items (s/coll-of ::item '()))

(s/conform ::items [["Tom" :lucky] ["Tim"]])
[["Tom" :lucky] ["Tim"]]

However, it looks like it passes the test but why the items are not parsed anymore?

Edit: This could be solved by switching from alpha7 to alpha10 release, where coll-of only takes one argument

Finally my spec looks like, having the previously described caveats:

(s/def ::my-spec (s/keys req-un [::items]))
1
Just a side note - #(= :lucky %) is better spec'ed as #{:lucky}. - Alex Miller

1 Answers

2
votes

spec is not designed to provide arbitrary data transformation (Clojure's core library can be used for that).

It is possible to achieve this using s/conformer but it is not recommended to use that feature for arbitrary transformations like this (it is better suited to things like building custom specs).