Liking cljdoc? Tell your friends :D

malli-select

Clojars Project cljdoc badge Tests

Create subschemas of malli-schemas using a spec2-inspired select notation. Works on Clojure and ClojureScript.

It's based on Rich Hickey's ideas from his talk "Maybe Not" about how spec-alpha2 might allow for schema reuse.

Quickstart

Follow along using deps-try:

$ deps-try io.github.eval/malli-select metosin/malli
# no install/docker
$ docker run -it --rm -v deps-try:/home/deps-try-user ghcr.io/eval/deps-try io.github.eval/malli-select metosin/malli

user=> (require '[malli-select.core :as ms]
                '[malli.core :as m :refer [form] :rename {form p}])
user=> (def Person
         [:map
           [:name string?]
           [:age pos-int?]
           [:addresses [:vector [:map
                                  [:street string?] [:zip string?]]]]])

;; require :name, everything else is optional
user=> (p (ms/select Person [:name]))
[:map
 [:name string?]
 [:age {:optional true} pos-int?]
 [:addresses
  {:optional true}
  [:vector
   [:map
    [:street {:optional true} string?]
    [:zip {:optional true} string?]]]]]

;; *if* any address is provided, it should at least have :street
user=> (p (ms/select Person [{:addresses [:street]}]))
[:map
 [:name {:optional true} string?]
 [:age {:optional true} pos-int?]
 [:addresses
  {:optional true}
  [:vector
   [:map [:street string?] [:zip {:optional true} string?]]]]]

;; example valid data:
;; {}, {:addresses []}, {:addresses [{:street "Main"}]}
;;
;; example invalid data:
;; {:addresses nil}, {:addresses [{}]}, {:addresses [{:street "Foo" :zip 1234}]}

;; any address provided should be a full address
user=> (p (ms/select Person [{:addresses ['*]}]))
;;
;; require all attributes of a person (shallow, i.e. address attributes become optional)
user=> (p (ms/select Person ['*]))
;; example valid data:
;; {:name "Foo" :age 18 :addresses [{}]}


;; remove any optional attribute
user=> (p (ms/select Person [{:addresses ['*]}] {:prune-optionals true}))
;; or shorter:
user=> (p (ms/select Person ^:only [{:addresses ['*]}]))
;; example valid data:
;; {:name :not-a-string}
;;
;; Typically you'd use this to generate only specific data:
user=> (require '[malli.generator :as mg])
user=> (mg/generate (ms/select Person ^:only [:name]))
{:name "sNeLdUI5KtPw"}

;; selecting something not contained in the schema:
user=> (ms/select Person [:a])
Execution error (ExceptionInfo) at malli-select.core/-fail! (core.cljc:7).
:malli-select.core/unknown-paths {:paths ([:a]), :available ([:addresses :street] [:addresses :zip] [:addresses] [:age] [:name])}

;; the unknown and available paths are also in the ex-data:
user=> (ex-data *e)
{:type :malli-select.core/unknown-paths,
 :data {:paths ([:a]),
        :available ([:addresses :street] [:addresses :zip] [:addresses] [:age] [:name])}}

;; bypass this check:
user=> (ms/select Person [:a] {:verify-selection false})
;; :verify-selection defaults to :throw (`:assert`, the pre-v0.8 spelling, still works);
;; :skip, nil and false disable the check.

;; other options: :log warns (stderr/console.warn) and continues...
user=> (ms/select Person [:a] {:verify-selection :log})
WARNING: :malli-select.core/unknown-paths {:paths ([:a]), :available (...)}
;; ...and a function gets the report, e.g.:
user=> (ms/select Person [:a] {:verify-selection #(log/warn "unknown paths" (:paths %))})

;; change the default via the dynamic var ms/*verify-selection*:
user=> (alter-var-root #'ms/*verify-selection* (constantly :log)) ;; CLJS: (set! ms/*verify-selection* :log)

note

Before v0.8 an invalid selection threw an AssertionError instead of an ExceptionInfo — see the CHANGELOG if you were catching it.

See the tests for more.

LICENSE

Copyright (c) 2026 Gert Goet, ThinkCreate. Distributed under the MIT license. See LICENSE.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close