Fork me on GitHub
#clojure-spec
<
2021-09-07
>
Franco Gasperino22:09:36

I have read that s/keys only supports keywords. I have a large external json message for which i would to perform some top-level schema validation. Rather than performing a keywordize-keys, which may descend past the desired schema and into embedded content, I was hoping to keep the message keys as strings and use validation upon them. From what i can see, this would be done similar to this:

(def last-name? #(= "last" %))
  (def first-name? #(= "first" %))
  (def age? #(= "age" %))

  (spec/def ::last-name (spec/tuple last-name? string?))
  (spec/def ::first-name (spec/tuple first-name? string?))
  (spec/def ::age (spec/tuple age? (spec/and pos-int? int?)))

  (spec/def 
   ::who
   (spec/coll-of 
    (spec/or :last ::last-name :first ::first-name :age ::age) 
    :kind map?
    :count 3
    :distinct true))
  
  (spec/explain ::who {"last" "smith" "first" "bob" "age" 10})
Is this the recommended path when using map keys which are strings, not keywords?

Alex Miller (Clojure team)22:09:51

This approach should work, you can simplify those preds using sets though, like #{“first”} (and at that point I would just inline them into ::first-name etc

Alex Miller (Clojure team)22:09:08

You also might find it useful to wrap s/nonconforming around the s/or to simplify the conformed structure

Franco Gasperino22:09:44

can you provide a case where s/conform ::who json-map would return something undesirable, where s/nonconforming would be wanted?

Alex Miller (Clojure team)23:09:58

(spec/or :last ::last-name :first ::first-name :age ::age) is going to conform to [:last ["last" "smith"]]

Alex Miller (Clojure team)23:09:49

(spec/nonconforming (spec/or :last ::last-name :first ::first-name :age ::age)) will conform to ["last" "smith"]

Alex Miller (Clojure team)23:09:16

if you use :into {} in your ::who spec, you can actually conform back to the original map if you want that

Franco Gasperino23:09:43

great to know. appreciated

Franco Gasperino23:09:43

didn't need the :into. wrapping the s/or in a nonconforming was enough to return a map when calling conform