12

I want to use a schema to validate a request object. One of the values in the map determines which other fields are valid.

For example, these would all be valid:

{ :name "jane" :type :dog :barking true }
{ :name "alan" :type :bird :cheeping true }
{ :name "bert" :type :fish :swimming true }

Some fields are common. But others depend upon the value of :type.

For example, this would be invalid:

{ :name "phil" :type :bird :barking false }

How can such schema be expressed?

I'm happy to use either clj-schema or Prismatic schema.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742

1 Answers1

14

You can use prismatic.schema's conditional to accomplish this:

(s/conditional #(= (:type %) :bird) {:type (s/eq :bird) :chirping s/Bool}
               #(= (:type %) :fish) {:type (s/eq :fish) :swimming s/Bool}
               ...
               :default  {:type (s/eq :animal) :existing s/Bool})
HyBRiD
  • 688
  • 4
  • 23
Arthur Ulfeldt
  • 90,827
  • 27
  • 201
  • 284
  • This looks great, thanks. Reading through the test code seems like a good idea as well. I have many fields that are common between types (i.e. like `:name` in my example) so would like to avoid repeating those in each cond branch. – Drew Noakes May 28 '14 at 17:09
  • 1
    I define a base type with the common fields, then `merge` it with the more specific type containing the specialized fields. prismatic schemas are just maps and you can manipulate them in all the normal ways. – Arthur Ulfeldt May 28 '14 at 17:45
  • Do you do the merge in each of the conditions, or can it be done at the top level? – Drew Noakes Jun 04 '14 at 15:12