Fork me on GitHub
#malli
<
2023-05-10
>
cap10morgan14:05:50

Would it make sense for a :map schema w/ keyword keys like [:map [:foo :string] [:bar :string]] decoding w/ the json-transformer when it encounters a map like {"foo" "whatever" "bar" "thingy"} to just turn those keys into keywords similar to what e.g. [:map-of :keyword :string] would do? It doesn't seem to do this today, but I'm wondering if a PR to enable it to would be welcome (or to find out that I'm doing it wrong).

escherize14:05:02

Good question: I would encourage using a key-transformer with the json-transformer. Something like

escherize14:05:47

#_:clj-kondo/ignore ;;nocommit
(require '[malli.core :as mc] '[malli.transform :as mtx] '[cheshire.core :as json])


(mc/decode [:map [:k :string]]
           (json/decode "{\"k\": \"v\"}")
           (mtx/transformer
            (mtx/json-transformer)
            (mtx/key-transformer {:decode keyword})))
;; => {:k "v"}

cap10morgan14:05:23

well, I can't b/c I don't necessarily want all keys transformed to keywords. just the ones that are spec'd as keywords in the schema

ack 1
Noah Bogart14:05:49

[:foo :string] is speccing foo as a keyword

Noah Bogart14:05:08

(mc/validate
  (mc/schema [:map [:foo :string]])
  {"foo" "a"})
;; => false

Noah Bogart14:05:32

(mc/validate
  (mc/schema [:map ['foo :string]])
  {'foo "a"})
;; => true

escherize14:05:10

True, however in my example above if I add a schema with a string as the key, it would still keywordize it

Noah Bogart14:05:32

oops, you're correct, i misunderstood

escherize14:05:45

Hmm. yeah, it’s tricky

cap10morgan14:05:56

yeah these schemas are for an internal representation that often comes in via an http api as json and we're using malli to coerce those to the internal representation

cap10morgan14:05:16

but some keys need to stay strings, so the key-transformer is a bit all-or-nothing

cap10morgan14:05:29

whereas the map schema already implicitly defines those types

escherize14:05:31

you could use a modified keyword schema

escherize14:05:09

on 2nd thought: maybe not for map keys, though

cap10morgan14:05:12

that's what I'm doing currently via an additional :map-of schema in an :and w/ the :map

cap10morgan14:05:18

but it's kind of clunky

cap10morgan14:05:28

and the :map already contains all the info it needs to do this

yes 1
cap10morgan14:05:31

hence my question

Noah Bogart15:05:24

looking at the source, seems the issue is that -json-decoders doesn't do anything special for maps

👍 1
Noah Bogart15:05:51

various other types do transformations: string to keyword, etc

escherize15:05:09

this is dumb:

escherize15:05:12

(defn m->m-decode-kws [map-schema]
  (let [kws (set (map name (filter keyword? (mut/keys map-schema))))
        kw-kws-fn (fn [m] (into {} (for [[k v] m] [(if (kws k) (keyword k) k) v])))]
    (mut/update-properties map-schema assoc :decode/mine kw-kws-fn)))

(mc/decode (m->m-decode-kws [:map [:k :int] ["j" :int]])
           {"k" 1, "j" 2}
           (mtx/transformer {:name "mine"}))
;; => {:k 1, "j" 2}

Noah Bogart15:05:14

and json-transformer only accepts map-of special stuff

escherize15:05:18

but it works

escherize15:05:48

it records which keys are keywords in kws, then calls keyword on them from the map’s custom transformer. verysweat_g

escherize15:05:15

I think the behavior could be rightly considered a bug though

cap10morgan15:05:49

Or at least an unimplemented feature

💯 2
Noah Bogart15:05:08

i think raising an issue on github is a good first step

👍 1
cap10morgan15:05:01

And now I realize you could make the exact same case for other literal value schemas like :enum or :=

cap10morgan15:05:09

but that's not an argument against 🙂

escherize17:05:32

fwiw, enum does decode its own keys:

(m/decode [:enum :k] "k" mt/json-transformer)
;;=> :k

cap10morgan17:05:37

Oh! Interesting!

cch115:05:50

Hi all, I’m new to malli but have some experience with spec tools - I’m committed to the switch from spec-tools. I’m struggling with the documentation around immutable registries -here’s my latest puzzle:

(malli/decoder [:map {:registry malli/default-registry}] malli.transform/json-transformer)
That blows up with a cryptic error message deep in the bowels of malli. Remove the reference to the default registry and it works as expected. Likewise, providing the registry as an option to decoder instead of inline in the spec works. I confess to being surprised by the malli registry and schema construction operations. An immutable registry is a big chunk of the value prop (along with transforms) of malli but I can’t master simple things like supplying a registry to transforms.

delaguardo15:05:59

try [:schema {:registry default-registry} [:map ,,,]]

cch115:05:23

(m/decoder [:schema {:registry m/default-registry} [:map]] mt/json-transformer) gives me the same error.

delaguardo16:05:25

could you add the error?

cch117:05:32

gats> (m/decoder [:schema {:registry m/default-registry} [:map]] mt/json-transformer)
Execution error (IllegalArgumentException) at malli.core/-property-registry (core.cljc:257).
Don't know how to create ISeq from: malli.registry$custom_default_registry$reify__28151
gats> 

cch114:05:51

I remain stumped and I’ll raise an issue for this. There seems to be a lot of undocumented behavior, perhaps even bugs, around supplying immutable registries when transforming.

escherize17:05:16

Will this work for you? (m/decoder :map {:registry m/default-registry} (mt/json-transformer))

cch117:05:50

Yes, that does work. But it’s strange that it’s possible to associate a registry with a map schema (clearly required for recursive structures) but not have it be generally supported. Or documented as such. In any case, I can change my approach to not associate registries with maps even with the map entries are spec’d in the schema. Instead, I’ll provide the registry everywhere else (decode, decoder, encode, encoder, explain …)