@raymcdermott noticed that none of the malli.util
helpers deref the refernce schemas, wrote an https://github.com/metosin/malli/issues/281. Before that, to use registry references, one needs to deref those manually, one option being:
(require '[malli.core :as m])
(require '[malli.registry :as mr])
(require '[malli.util :as mu])
(mr/set-default-registry!
(mr/composite-registry
(m/default-schemas)
{:asset/id string?
:asset/status [:enum :initialized :accepted :active :revoked :failed]
:asset/asset [:map
:asset/id
:asset/status
[:asset/asset {:optional true} [:ref :asset/asset]]]}))
(def org-asset
(mu/merge
(m/deref :asset/asset)
[:map [:asset/org [:= "org"]]]))
org-asset
;[:map
; [:asset/id :asset/id]
; [:asset/status :asset/status]
; [:asset/asset {:optional true} [:ref :asset/asset]]
; [:asset/org [:= "org"]]]
(def user-asset
(mu/merge
(m/deref :asset/asset)
[:map [:asset/user [:= "user"]]]))
user-asset
;[:map
; [:asset/id :asset/id]
; [:asset/status :asset/status]
; [:asset/asset {:optional true} [:ref :asset/asset]]
; [:asset/user [:= "user"]]]
also, the declarative :merge
would allow registries like this:
{:asset/id string?
:asset/status [:enum :initialized :accepted :active :revoked :failed]
:asset/asset [:map
:asset/id
:asset/status
[:asset/asset {:optional true} [:ref :asset/asset]]]
:asset/org-asset [:merge :asset/asset [:map [:asset/org [:= "org"]]]]
:asset/user-asset [:merge :asset/asset [:map [:asset/user [:= "user"]]]]}
@shem, you example doesn’t seem legit syntax. But for JSON, you need to defin :decode/json
key. Something like:
(m/decode
[:map
[:Duration string?]
[:Start {:decode/json (fn [x] (mt/-string->long (last (re-find #"(\d+)" x))))} string?]]
{:Duration "kikka"
:Start "new Date(1413147600000)"}
(mt/json-transformer))
;{:Duration "kikka"
; :Start 1413147600000}
if your new Date
is coming from js, it’s a string of Mon Oct 13 2014 00:00:00 GMT+0300 (Eastern European Summer Time) {}
, and you need to run it via some date-transforming fn. hope this helps.
Would it be a breaking change if m/deref
was recursive and would would return the input schema in case it was not a RefSchema
? I would like it to be:
(require '[malli.core :as m])
(m/schema [:schema [:schema [:schema [:map [:x int?]]]]])
; => [:schema [:schema [:schema [:map [:x int?]]]]]
(m/deref (m/schema [:schema [:schema [:schema [:map [:x int?]]]]]))
; => [:map [:x int?]]
, instead of:
(m/deref (m/schema [:schema [:schema [:schema [:map [:x int?]]]]]))
; => [:schema [:schema [:map [:x int?]]]]
also:
(m/deref [:map [:x int?]])
; => [:map [:x int?]]
instead of:
(m/deref [:map [:x int?]])
; Execution error (IllegalArgumentException)
; No implementation of method: :-deref of protocol: #'malli.core/RefSchema found for class: malli.core$_map_schema$reify$reify__4587
Hi guys! I’m looking for any pointers on how to transform keys (ie snake to kebab) in nested schemas. I have the following schema:
(def Invoice
[:map {:registry {::address Address
::tax-line TaxLine}}
[:issue_date [string? epoch]]
[:series string?]
[:sequence [string? {:decode/string mt/-string->long}]]
[:currency [string? (lookup-ref :currency/code)]]
[:customer_legal_name string?]
[:customer_tax_id string?]
[:customer_tax_id_type [string? (lookup-ref :tax-id/type)]]
[:customer_email {:optional true} string?]
[:customer_address {:optional true} ::address]
[:tax_lines [:vector ::tax-line]]
[:subtotal [string? {:decode/string mt/-string->double}]]
[:tax [string? {:decode/string mt/-string->double}]]
[:total [string? {:decode/string mt/-string->double}]]])
When i do:
(defn coerce-request
[schema req]
(if (m/validate schema req)
(m/decode schema
req
nil
(mt/transformer
mt/json-transformer
mt/string-transformer
{:name :epoch}
{:name :lookup-ref}
(mt/key-transformer {:decode k/->kebab-case})))
(-> schema
(m/explain req)
(me/humanize))))
customer_address
map keys are not affected
@eraad decoding is a process of taking an invalid value and transforming it to a valid (EDN) value. Because of this, the key-transformer
runs the transformations on :enter
phase. All the top-level map keys are transformed into invalid (kebab)-values and the :customer_address
gets a value of :customer-value
. After this, the keys don’t match the schema and the child decoders are no-op.
if you need to transform values out from the valid (EDN) definitions, you can use m/encode
. the key-transformer
runs in :leave
phase there, in your case as the last thing as it’s also last transformer in the chain.
Hi @ikitommi thank you for the response :thumbsup:
here’s a minimal sample:
(m/decode
[:map {:registry {::address [:map [:street string?]]}}
[:customer_address ::address]]
{:customer_address {:street "hämeenkatu"}}
(mt/transformer
(mt/key-transformer {:decode #(-> % name str/upper-case keyword)})))
; => {:CUSTOMER_ADDRESS {:street "hämeenkatu"}}
(m/encode
[:map {:registry {::address [:map [:street string?]]}}
[:customer_address ::address]]
{:customer_address {:street "hämeenkatu"}}
(mt/transformer
(mt/key-transformer {:encode #(-> % name str/upper-case keyword)})))
; => {:CUSTOMER_ADDRESS {:STREET "hämeenkatu"}}
Nice, thanks!
I’m really loving Malli, tried Spec+Spec-tools for a bit but then tried Malli and its great
in your case, you can write your own rename-keys like this (running on :leave
in decode):
(m/decode
[:map {:registry {::address [:map [:street string?]]}}
[:customer_address ::address]]
{:customer_address {:street "hämeenkatu"}}
(mt/transformer
{:decoders {:map {:leave (mt/-transform-map-keys #(-> % name str/upper-case keyword))}}}))
; => {:CUSTOMER_ADDRESS {:STREET "hämeenkatu"}}
“on :decode
, for :map
s run this function f
on :leave
”. transformers are quite simple in the end 😉
Coo, I was not being aware of :enter and :leave, that’s the missing part