malli

2026-06-10T17:27:39.193349Z

Hi, does anyone know if there is there an idiomatic way to validate a map where: 1. Each entry must conform to one of N specific k/v pairings 2. m/explain produces a path to the specific offending entry, and/or offending value within that entry?

(require '[malli.core :as m])

;; A map where each entry must be one of these valid k/v pairings:
;; keyword/int
;; number/string
;; string/map

;; We want to validate a map like:
{12  "hello"
 :z  42
 "a" 1}

(def map-entry-schema
  [:orn
   [:foo [:tuple keyword? int?]]
   [:bar [:tuple number? string?]]
   [:baz [:tuple string? map?]]])

;; Works fine at the level of map-entry
(m/validate map-entry-schema [12 "hello"]) ;=> true
(m/validate map-entry-schema [:z 42])      ;=> true
(m/validate map-entry-schema ["a" 1])      ;=> false

;; The problem: how to lift this to map level with `m/explain` support?


;; Option A: :fn + every? — enforces pairing but `m/explain` gives no path to bad entry - the path is to the whole map
(def map-schema-a
  [:and
   :map
   [:fn #(every? (fn [entry] (m/validate map-entry-schema (vec entry))) %)]])


;; Option B: :map-of with :or — gives explain paths but loses pairing
;; enforcement, so would result in false positives
(def map-schema-b
  [:map-of
   [:or :int :keyword :string]
   [:or :string :int :map]])

(m/validate map-schema-b {:12 "hello" :bar-key 42}) ;=> true
(m/validate map-schema-b {:foo-key 42 :bar-key "oops"})  ;=> true ; not actually true, pairing not enforced

2026-06-15T16:48:41.433699Z

Oh yeah I do remember seeing that malli.experimental.validate was added a few months ago, thanks for the reminder I will experiment with that as well. :dmap-of looks awesome! Thank you for drafting that I will definitely be utilizing it

😄 1
2026-06-15T18:00:29.981249Z

I have no experience implementing -transformer so I left that as the big TODO.

2026-06-15T18:07:59.501729Z

yeah transformers are tricky … more than meets the eye

2026-06-15T18:08:31.431269Z

This comment from the draft piqued my interest: > ;; note: just like :map-of, we have :in == [“a”] here, even though we blame the key Is there currently any way to reliable know if the :value entry in an error map is a map key or not? I’ve been under the assumption that the answer is no, and I’ve been using a custom function (with the result of m/explain ) to highlight offending key values in maps (for custom warning callouts):

(defn- target-key?
  "Takes an error map from malli.core/explain result and the value that produced
   that result, and determines if the `:value` in the error map is a map key."
  [{:keys [value in path] :as error}
   x]
  (boolean
   (when (coll? x)
     (let [vectorized (walk/postwalk #(if (seq? %) (vec %) %) x)
           m          (->> in drop-last (get-in vectorized))
           k          (when (map? m) (->> in last (find m) first))]
       (and (= k value)
            (not= 1 (last path)))))))


(let [x {"a" "a"}]
  (-> (m/explain [:map-of :int :string] x)
      :errors
      first
      (target-key? x)))
;; => true

;; example with more complex nesting
(let [v [{:a {"a" "a"}}]]
  (some-> (m/explain [:vector [:map-of
                               :keyword
                               [:map-of :int :string]]] v)
          :errors
          first
          (target-key? v)))
;; => true

(let [v [{:a {1 :a}}]]
  (some-> (m/explain [:vector [:map-of
                               :keyword
                               [:map-of :int :string]]] v)
          :errors
          first
          (target-key? v)))
;; => false
The approach above has seemed to work ok so far, but of course I’m not sure about different edge cases as I’m still very much a malli noob. If there is in fact a reliable way to determine this, it would be cool if this was included in the error map from m/explain:
{:path   [0]
 :in     ["a"]
 :schema malli.core$_simple_schema$reify$reify__10018@3402b508
 :value  "a"
 :map-key? true}

2026-06-15T18:12:22.174049Z

I have similar questions after this exercise. The :value was sometimes set to the val even tho the key was to blame.

2026-06-15T18:12:46.492069Z

I guess the (peek path) will tell you whether the :value matters or not.

2026-06-15T18:13:14.334049Z

if it's 0 then just use (peek in) to find the problematic value.

2026-06-15T18:13:55.708619Z

I opened this related issue about humanization in this case https://github.com/metosin/malli/issues/1290

2026-06-15T18:24:22.407689Z

Happy to see there is a fresh issue on the humanization part. wrt to peek thank you that is a good idea … I will use that to refactor my clumsy target-key fn haha.

👍 1
2026-06-14T05:44:49.142169Z

I think humanize has more support for this sort of thing. See :error/path https://github.com/metosin/malli#custom-error-messages

2026-06-14T06:04:56.634809Z

I drafted a schema that mostly works as you specified here https://github.com/frenchy64/malli/pull/54/changes

2026-06-12T21:08:36.381279Z

FWIW I think this is beyond :map and :map-of. You'd need to create your own schema.