Clojure.spec: Conforming values



I am looking for ways to use spec to control the evolution of an API we don’t control.

Say we have an API like:

{:username "abc"
 :signed-up "2018-02-15" }

That at some point becomes:

{:username "abc"
 :sign-on {:yy 2018 :mm 2 ::dd 15}}

And I need to handle both.

Writing a spec to know whether either is a valid form is trivial; this said, once I know that it’s either one or the other, I need to handle both in code.

So I was thinking that maybe s/conform was what I was looking for: a way to reduce one form to the other, so that my code only has to deal with a canonical, spec-validated input form. But how do I do this? or is it the wrong tool for the job?



Create an s/or spec of the two.

Now call conform and it will return you the tag of the or that matched. So you know if you have a case of the first or the second.

Create a function that converts one to the other. When conform says the data is of the other tag you expect, then convert it with your conversion function.


Yes, this is exactly what conform is for!

As didibus mentioned make an “or” spec with both variants, e.g. ::string-data and ::map-date.
I used a multimethod to dispatch on the returned label

(defmulti convert-date first)  ;; dispatch on "first" which is the label returned by conform, either ::string-data or ::map-date

So say you like the map version of the date better, then

(defmethod convert-date ::map-date [date] date)  ;; do nothing
(defmethod convert-date ::string-date [date] (parse date))

You might also be able to parse the string date with spec. But that’s a little more work to get right (in my experience).

I have posted this here before, but that case is very similar to your problem:


Edit: realized that the keyword is also different (::signed-up vs ::sign-on). The same technique will also work, but you’ll have to dispatch on the type of the outer map.