Home

Awesome

schema-bijections

A first draft of a library for bijecting prismatic schemas.

Obtention

[com.gfredericks/schema-bijections "0.1.3"]

Rationale

I use prismatic/schema a lot, and was excited when I learned about the coercers feature because I thought it would solve the problem of using boilerplate or messy shortcuts to convert data into fluent clojure representations (e.g., keyword keys, kebab-cased keys, joda types for timestamps, java.util.UUID instances for UUID's, etc.).

A coercer is a function produced from a schema, which can convert objects that "almost" match the schema to objects that actually do match the schema. I had quickly figured out how to use coercers to solve all of the problems mentioned above, but I quickly noticed some drawbacks.

My primary use case was for specifying a JSON HTTP API. I used schemas to describe the input and output data structures, in their clojure-fluent form. Among the drawbacks:

So this library is an attempt to solve the problem of transforming data between different formats in a more first-class way.

The motivating use is to have a schema that describes the clojure-fluent data format for something, and then specify schema transformation functions to convert that schema to the variant format. The library composes the functions in much the same way that prismatic's coercers do, but the final result is:

which I believe addresses the drawbacks listed above.

Examples

(require '[schema.core :as s]
         '[camel-snake-kebab.core :as csk]
         '[com.gfredericks.schema-bijections :as sb])

(def camelize-keys (sb/transform-keys csk/->camelCase))

(defn jsonify-schema
  [schema]
  (let [{:keys [left left->right right->left]}
        (sb/schema->bijection schema
                              [sb/stringify-uuids
                               sb/stringify-keys
                               camelize-keys])]
    {:json-schema left
     :to-json right->left
     :from-json left->right}))

(def User
  {:id s/Uuid
   :full-name s/Str
   :comments [{:id s/Uuid, :text s/Str}]})

(let [{:keys [json-schema from-json to-json]}
      (jsonify-schema User)]
  (def json->User from-json)
  (def User->json to-json)
  (def UserJson json-schema))

UserJson
=> {#schema.core.RequiredKey{:k "id"}
    #"^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$",

    #schema.core.RequiredKey{:k "fullName"}
    java.lang.String,

    #schema.core.RequiredKey{:k "comments"}
    [{#schema.core.RequiredKey{:k "id"}
      #"^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$",

      #schema.core.RequiredKey{:k "text"}
      java.lang.String}]}

(def my-user {:id #uuid "0e90ab35-c550-40ff-ba8a-9e7da774c62c"
              :full-name "Tom Hanks"
              :comments [{:id #uuid "75d450b5-ebc7-43e5-a48c-b11f93e5f7dd"
                          :text "Leave me be."}]})

(User->json my-user)
=> {"comments"
    [{"id" "75d450b5-ebc7-43e5-a48c-b11f93e5f7dd",
      "text" "Leave me be."}],
    "fullName" "Tom Hanks",
    "id" "0e90ab35-c550-40ff-ba8a-9e7da774c62c"}

(= my-user (-> my-user User->json json->User))
=> true

TODOs, caveats, etc.

License

Copyright © 2015 Gary Fredericks

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.