metosin / malli

High-performance data-driven data specification library for Clojure/Script.
Eclipse Public License 2.0
1.51k stars 212 forks source link

Custom key mapping function for `malli.json-schema/transform`? #273

Open mvarela opened 4 years ago

mvarela commented 4 years ago

Hi! I've just picked up Malli for a project, and I was wondering if there is a way to use a custom key-fn (as in clojure.data.json or Jsonista's mappers) when converting to JSON Schema. My spec has namespaced keys, and the namespaces are dropped in the exported JSON Schema. For a bit of context, I'm working with Kafka streams, and will be feeding some of the topics to Elasticsearch. I would like to use their specified naming conventions when sending the data there (along with the corresponding schema). I can easily get the data as I need it this via a custom serde, but I'd rather not have to process the exported JSON schema, too.

Thanks for any pointers you can provide, and for this awesome library!

rschmukler commented 4 years ago

@mvarela I have needed similar functionality in my own applications have ended up writing a custom JSON transformer that adheres to my preferences.

For an example on how this can be done, you can check it out here.

Just for some extra context on the link above, that transformer also handles working with namespaced maps, and namespaced keywords functioning as enum values. Eg:

{:person/name "Bob"
 :person/favorite-color :color/blue
 :person/age 40
 :pet/name "Rosko"}

;; Becomes
{"name" "Bob"
 "favorite_color" "blue"
 "age" 40
 "pet_name" "Rosko"

Transformers also compose, so you could do a transformer that encodes the key and compose it with the stock malli json transformer before or after it.

mvarela commented 4 years ago

@rschmukler, thanks for the tip and example!

ikitommi commented 4 years ago

the current mapping is done using a multimethod, so it should be quite straightforward to override:

(defmethod accept :map [_ _ children _]
  (let [required (->> children (filter (m/-comp not :optional second)) (mapv first))]
    {:type "object"
     :properties (apply array-map (mapcat (fn [[k _ s]] [k s]) children))
     :required required}))

that said, there is also a ::definitions and ::transform options for transformations, could have something like ::transform-key too.