Malli Data Validation
Malli validates data against schemas. Schemas are Clojure data structures.
Quick Validation
clojure
(require '[malli.core :as m])
(require '[malli.error :as me])
;; Validate
(m/validate [:map [:name :string] [:age :int]]
{:name "Alice" :age 30})
;; => true
;; Get errors
(-> [:map [:name :string] [:age :int]]
(m/explain {:name "Alice" :age "thirty"})
(me/humanize))
;; => {:age ["should be an integer"]}
Quick Coercion
clojure
(require '[malli.transform :as mt])
;; Decode string input to proper types
(m/decode [:map [:port :int] [:active :boolean]]
{:port "8080" :active "true"}
(mt/string-transformer))
;; => {:port 8080, :active true}
;; Coerce = decode + validate (throws on error)
(m/coerce [:map [:id :int]] {:id "42"} (mt/string-transformer))
;; => {:id 42}
Common Schema Patterns
Maps with Required/Optional Keys
clojure
[:map
[:id :uuid] ;; required
[:name :string] ;; required
[:email {:optional true} :string] ;; optional
[:role {:default "user"} :string]] ;; optional with default
Constrained Values
clojure
[:string {:min 1 :max 100}] ;; string length 1-100
[:int {:min 0 :max 150}] ;; integer range
[:enum "draft" "published"] ;; one of these values
[:re #".+@.+\..+"] ;; regex match
Collections
clojure
[:vector :int] ;; vector of ints [:set :keyword] ;; set of keywords [:map-of :keyword :string] ;; map with keyword keys, string values [:tuple :double :double] ;; fixed [x, y] pair
Unions and Conditionals
clojure
;; Simple union
[:or :string :int]
;; Nilable
[:maybe :string] ;; string or nil
;; Tagged union with dispatch
[:multi {:dispatch :type}
[:user [:map [:type [:= :user]] [:name :string]]]
[:admin [:map [:type [:= :admin]] [:role :string]]]]
Nested Structures
clojure
[:map
[:user [:map
[:name :string]
[:address [:map
[:city :string]
[:zip :string]]]]]]
Performance: Cache Validators
clojure
;; BAD - creates validator every call (defn process [data] (when (m/validate schema data) ...)) ;; GOOD - cached validator (def valid? (m/validator schema)) (defn process [data] (when (valid? data) ...)) ;; Same for decoders (def decode-request (m/decoder schema (mt/string-transformer))) (def coerce-request (m/coercer schema (mt/string-transformer)))
Key Gotchas
- •decode doesn't validate - returns invalid data as-is. Use
coercefor safety. - •Maps are open by default - extra keys allowed. Use
{:closed true}to reject them. - •Keys are required by default - use
{:optional true}for optional keys.
Detailed References
- •Schema Syntax - All builtin types, properties, operators
- •API Reference - Core functions with signatures
- •Registries - Reusable schema patterns