π
Fantastic! Going to try these out now!
what a brilliant/horrible idea: add optional :object
schema, which allows maps to be described using clojure maps. Kinda like data-specs but for malli:
[:object
{"name" [:required :string]
"age" [:required :number :positive :integer]
"email" [:string :email]
"website" [:string :url]
"createdOn" [[:date {:default "2020-18-10"}]]
"address" [:object
{"street" [[:string {:min 1}]]
"zip" [:int]}]}]
or:
[:object
{"name" [:and :required :string]
"age" [:and :required :number :positive :integer]
"email" [:and :string :email]
"website" [:and :string :url]
"createdOn" [:date {:default "2020-18-10"}]
"address" {:street [:string {:min 1}]
:zip :int}}]
a bit like Schema, but more EDN-like
βοΈ. simple formats like Schema and data-spec are kinda easy, but not simple: the core utilties (`select-keys`, assoc
etc.) almost work, unless you have a wrapper for keys like ds/opt
or s/optional-key
in case they donβt. Also, value wrappers are needed to add visible properties/meta-data to schemas. And there is no order for keys. But, super nice for many things like defining inlined route parameters:
["/plus"
{:get {:summary "plus with spec query parameters"
:parameters {:query {:x int?, :y int?}}
:responses {200 {:body {:total int?}}}
:handler handle-plus}]
if there was a litemalli
coercion in reitit, one could swap the data-spec apps almost 1:1 to it.
I prefer #1 to #2; the latter is making nested map notation implicit and special for :object
and I don't know if it's worth it.
That reitit example could just as easily be solved by a utility function that takes the concise nested map notation and converts it to a tree of :object
someone should write malli-tools
;)
:grinning_face_with_one_large_and_one_small_eye:
Does :object
offer different semantics or tradeoffs than :map
? Or would it deref/merge down to a :map
schema? Or would we have two things that are the same but different? :]
To be clear, I prefer writing the map notation and was always confused why :map
used vectors; but that ship has sailed... or has it?
we're in a dynlang, we can do everything we want ;)
I'm thinking :object
could be just a nice alternative syntax that would "compile down" to a :map
. But maybe I'm just missing the point. :)
why it is called object though?
like JavaScript object?
I was thinking like: wow, is this a schema to describe Java objects like bean-stuff?
anyone not working on front-ends might think the same
vector syntax for :map
allows simple way to define properties for it and retains the order of keys. But yes, we can add more schemas to build things in different ways. Could have also optional :`json-schema` that allows any JSON Schema in it etc.
the name could be anything, just flushing stuff from my brain (after a vacation).
IMO, it would be confusing to see both :map
and :object
somewhere in my system output and they were semantically the same thing (a representation of known key-value mapping); so the way I see it:
1. if we want an alternative writing notation, why not just make it a utility function? (instead of introducing a new kind of schema)
2. if we want an alternative notation that is a data-literal (utility functions need runtime support), then we could introduce an alternative name (like :object
); but it could just as easily be :kv
or :map2
; or even why not extend existing :map
to support both kinds of notations (vectors or maps)
3. if this is something similar to :map
but offers possibly different semantics, than currently those semantics aren't clear to me; otherwise it'd be great if we didn't have two things that represent essentially the same thing (this would make merging, etc. more complicated to deal with later on)
(This is also why I like having :map
and :map-of
, because they represent different semantics; even though they are using the same internal datastructure)
Related to 2, I think they should be kept separate (if implemented). If :map
supported both syntax, there would be ambiguity issues:
[:map
{:id :int
:title :string}]
is that empty map with props? or map with no props but with keys?If I would implement that today, it would be a separate ns, malli.object
etc, with a Schema
impl, a helper and a extra registry snipplet so one can merge the schemas into a registry easily:
(require '[malli.core :as m])
(require '[malli.object :as mo])
(def registry (merge (m/default-schemas) (mo/schemas)))
(m/schema
[:object
{:id int?
:address [:object {:street string?}]}]
{:registry registry})