This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-04
Channels
- # announcements (1)
- # architecture (16)
- # asami (30)
- # babashka (1)
- # beginners (17)
- # calva (4)
- # clj-kondo (4)
- # clojure (32)
- # clojure-austin (2)
- # clojure-dev (16)
- # clojure-europe (33)
- # clojure-mexico (1)
- # clojurescript (40)
- # data-science (9)
- # emacs (33)
- # fulcro (1)
- # jackdaw (4)
- # malli (25)
- # off-topic (34)
- # pathom (23)
- # react (16)
- # reagent (8)
- # releases (5)
- # vim (46)
interesting, would that work with complex types like [:or [:map [:x :int]] [:map [:y :string]]]
?
this is a bug, right?
(def t (mt/transformer
{:name :hello
:decoders {:string (constantly "HELLO")}}))
(def schema-1 [:map [:a :string] [:b :string]])
(def schema-2 [:or [:map [:a :string] [:b :string]]])
(def schema-3 [:or [:map [:a :string]]])
(def schema-4 [:map [:a :string]])
(def v {:a 1234})
(m/decode schema-1 v t) ;; {:a "HELLO"}
(m/decode schema-2 v t) ;; {:a 1234}
(m/decode schema-3 v t) ;; {:a "HELLO"}
(m/decode schema-4 v t) ;; {:a "HELLO"}
It’s a feature. :or
transformer validates the result after transformation, if the result is not valid, it will ignore that branch. This could be improved, e.g.
1. take the first branch which becomes valid after the transformation (current)
2. if none is valid (new)
a. take the first branch OR
b. take the first branch where the value changed OR
c. take the branch is is “most valid” with some value distance metering
… none of these would make it bullet proof, but maybe suck less.
aha, I see.. hmm, tricky.. my case requires me to have multiple transformation passes (default values, some type coercions, etc..) before it's valid. of the cases you outlined 2b would get me closest, but I still think it would fail. previously I tried using a multi-schema instead of or since all objects have an identity key, but ran into the issue mentioned in the FIXME comment; dispatch value is not valid until transformed
there is an example for the dispatch key thing with :multi
on README:
:dispatch
values should be decoded before actual values:
(m/decode
[:multi {:dispatch :type
:decode/string #(update % :type keyword)}
[:sized [:map [:type [:= :sized]] [:size int?]]]
[:human [:map [:type [:= :human]] [:name string?] [:address [:map [:country keyword?]]]]]]
{:type "human"
:name "Tiina"
:age "98"
:address {:country "finland"
:street "this is an extra key"}}
(mt/transformer mt/strip-extra-keys-transformer mt/string-transformer))
;{:type :human
; :name "Tiina"
; :address {:country :finland}}
Regarding or. Couldnt you merge all the cases and transform using that?
by no means bulletproof either, but fits with the best effort mantra of transformers
> Couldnt you merge all the cases and transform using that? > could you describe how this would work?
given or
schema ?schema, do (reduce mu/merge nil (m/children ?schema))
and call transform on that
and you wouldnt validate the result
i do this when generating datomic pull patterns for or schemas, so i end up overfetching and clean up using strip-keys
mu/merge
overrides if the values are of different types (like normal merge):
(->> [:or
[:map [:x :int]]
[:vector :int]
:int]
(m/children)
(reduce mu/merge nil))
; => :int
even if they are maps, I don’t think this would work:
(->> [:or
[:map [:x :int]]
[:map [:x :string]]
[:map [:x [:maybe :uuid]]]]
(m/children)
(reduce mu/merge nil))
; => [:map [:x [:maybe :uuid]]]
using me/union
might be ok here:
(->> [:or
[:map [:x :int]]
[:map [:x :string]]
[:map [:x [:maybe :uuid]]]]
(m/children)
(reduce mu/union nil))
; => [:map [:x [:or [:or :int :string] [:maybe :uuid]]]]
would work at least in your example:
(->> [:or [:map [:a :string] [:b :string]]]
(m/children)
(reduce mu/union nil))
; => [:map [:a :string] [:b :string]]
here, the order matters:
(map
(m/decoder
(->> [:or
[:map [:x :int]]
[:map [:x :string]]
[:map [:x [:maybe :uuid]]]]
(m/children)
(reduce mu/union nil))
(mt/string-transformer))
[{:x 1} {:x "1"}, {:x "kikka"}, {:x (str (random-uuid))}])
;({:x 1}
; {:x 1}
; {:x "kikka"}
; {:x "ed4e918e-2e4f-49d0-a0ee-d9805ffe6384"}) ;; already ok at the string branch
swapping the order:
(map
(m/decoder
(->> [:or
[:map [:x [:maybe :uuid]]]
[:map [:x :string]]
[:map [:x :int]]]
(m/children)
(reduce mu/union nil))
(mt/string-transformer))
[{:x 1} {:x "1"}, {:x "kikka"}, {:x (str (random-uuid))}])
;({:x 1}
; {:x 1}
; {:x "kikka"}
; {:x #uuid"ed4e918e-2e4f-49d0-a0ee-d9805ffe6384"})
summary:
1. mu/union
gets you close, but need to be mindful about the order
2. would need more testing to verify that it doesn’t give wrong answers, e.g. with nested values
3. making default transformers depend on malli.util
it would mean than in CLJS, anyone using any transformations, would need to pull a lot of extra code for this, making the js bundle size much larger by default. Should be optional
4. if you think this would solve your case, I would be ok with:
a. adding an option to :or
to override the default transformation logic with a custom one
Thanks for the thorough explanation and outlining the steps going forward. I've gone through our usage of or-schemas. Knowing what I now know about the semantics, I've yet to find a single case where a multi spec isn't preferable. If I find one I'll send a PR.