A very simple library for performing powerful map transformations in Clojure.
The problem that this library solves is needing to transform maps from one domain (e.g. results fetched from a database) into another domain (e.g. your application's data model).
This process generally requires 3 steps:
- Select, delete or insert certain keys from the input map.
- Apply transformations to values in the map.
- Rename keys.
mapx provides just one core function to achieve this in a declarative,
clear manner: mapx.core/transform
.
Add the following to your project.clj
:
[chrisjd/mapx "0.2.2"]
For the examples below:
user> (require '[mapx.core :as mx])
nil
Overwriting or inserting keys:
user> (mx/transform {:a 1 :b 2 :c 3} :assoc {:a 123 :d 4})
{:a 123, :b 2, :c 3, :d 4}
Inserting missing keys:
user> (mx/transform {:a 1 :b 2 :c 3} :or {:a 123 :d 4})
{:a 1, :b 2, :c 3, :d 4}
Selecting keys:
user> (mx/transform {:a 1 :b 2 :c 3} :select [:a :c])
{:a 1, :c 3}
Deleting keys:
user> (mx/transform {:a 1 :b 2 :c 3} :delete [:a])
{:b 2, :c 3}
Updating values:
user> (mx/transform {:a 1 :b 2 :c 3} :update {:a inc, :c (partial * 20)})
{:a 2, :b 2, :c 60}
Renaming keys:
user> (mx/transform {:a 1 :b 2 :c 3} :rename {:a :x, :b :y, :c :z})
{:x 1, :y 2, :z 3}
All together:
user> (mx/transform {:a 1 :b 2 :c 3}
:or {:a 123, :d 456}
:select [:a :c :d]
:delete [:c]
:update {:a str}
:rename {:a :foo})
{:foo "1", :d 456}
Projection:
user> (mx/transform {:a 1 :b 2 :c 3}
:project {:a :x :c :z})
{:x 1, :z 3}
Update and project:
user> (mx/transform {:a 1 :b 2 :c 3}
:update {:a inc, :c dec}
:project {:a :x :c :z})
{:x 2, :z 2}
A typical example of how mapx can be used in real projects:
(defn make-account
"Create a new account."
[& {:as opts}]
{:post [(s/valid? :data/account %)]}
(transform opts
:or {:role :user}
:project {:email :data.account/email
:password :data.account/password
:role :data.account/role
:user :data.account/user
:subscription :data.account/subscription}))
Here, we provide a default value for :role
, but expect the other
fields to be specified. The post-condition on the function uses
spec to ensure that what we
produce is a well-formed account.
Using with speconv to succinctly define conversion functions to map the same data between two distinct (and spec-validated) contexts:
(conversion :db/subscription :data/subscription
[in]
(transform in
:project {:subscription/expires :data.subscription/expires
:subscription/period-charge :data.subscription/period-charge
:subscription/period-length :data.subscription/period-length
:db/id :data/entity-id}))
(conversion :data/subscription :db/subscription
[in]
(transform in
:project {:data.subscription/expires :subscription/expires
:data.subscription/period-charge :subscription/period-charge
:data.subscription/period-length :subscription/period-length
:data/entity-id :db/id}))
The benefit being that we explicitly state which data is to be carried across domains without the risk of leaking sensitive or internal information accidentally.
Copyright © 2017 Chris J-D
Distributed under the MIT License: https://opensource.org/licenses/MIT