supdate
is a small Clojure/ClojureScript library
for transforming nested data structures in a straightforward and efficient way.
supdate
lets you express nested transformations as data structures which match the schema of the input,
and then executes these transformations efficiently.
You could say supdate
is to data transformation what plumatic/schema
is to data validation. It plays in the same arena as Specter
, complementing
it nicely for many basic cases where Specter would be too much.
(require '[vvvvalvalval.supdate.api :as supd])
Here's a basic example (it's interactive, feel free to modify the code!):
(require '[clojure.string :as str])
;; Our example uses some string functions
(require '[clojure.string :as str])
;; Let's define some example data
(def input
{:bands [{:band/id 3141
:band/name "Led Zeppelin"
:band/members [{:person/name "robert plant"}
{:person/name "jimmy page"}
{:person/name "john bonham"}
{:person/name "john paul jones"}]}
{:band/id 8242
:band/name "The White stripes"
:band/members [{:person/name "jack white"}
{:person/name "meg white"}]}]})
;; ... then transform it:
(supd/supdate
input
{:bands [{:band/name str/upper-case
:band/members [{:person/name str/capitalize}]}]})
supdate
works by defining transforms as data structures.
The most elementary transform you can encounter is just a function:
(supd/supdate 0 inc)
(supd/supdate
"Jimmy Page"
(fn [s] (str/split s " ")))
(supd/supdate
[2 3 5 7 11 13 17 19]
[inc])
(supd/supdate
(range 12)
[#(* % %)])
(supd/supdate
{:name "Alice" :age 7}
{:age inc})
Note that the transformation only occurs if the key is present!
(supd/supdate
{:name "Ilúvatar"}
{:age inc})
Finally, if you use false
as a value instead of a transform, it means you want the key to be dissoc'ed:
(supd/supdate
{:name "Alice" :age 7}
{:age false})
(supd/supdate
" jimmy page "
[str/trim str/capitalize])
What if you want to do both, chaining tranforms and applying them in sequences? Well, you can use 2 vectors:
(supd/supdate
(range 10)
[[inc inc inc]])
supdate
really becomes interesting when you start nesting transforms together:
(def input1
{:band/members [{:id 1 :name ["jimmy" "page"] :plays "guitar"}
{:id 2 :name ["robert" "plant"] :plays "voice"}
{:id 3 :name ["john" "paul" "jones"] :plays "bass"}
{:id 4 :name ["john" "bonham"] :plays "drums"}]})
(supd/supdate
input1
{:band/members [{:id false
:name [[str/capitalize]
#(str/join " " %)]
:plays keyword}]})
supdate
fast?Yes! Supdate will tend to outperform code hand-written using update-in
or assoc-in
, using several optimizations.
It will avoid doing several 'deep dives' in the data, doing all it has to do once at a given path.
What's more, supd/supdate
is actually a macro, leveraging static information on a best-effort basis to compile to efficient code.
Finally, you can also use supd/compile
to pre-compile transforms, and achieve a similar effect more dynamically:
(def input
{:bands [{:band/id 3141
:band/name "Led Zeppelin"
:band/members [{:person/name "robert plant"}
{:person/name "jimmy page"}
{:person/name "john bonham"}
{:person/name "john paul jones"}]}
{:band/id 8242
:band/name "The White stripes"
:band/members [{:person/name "jack white"}
{:person/name "meg white"}]}]})
;; Pre-compiling our transform
(def prettify-bands
(supd/compile
{:bands [{:band/name str/upper-case
:band/members [{:person/name str/capitalize}]}]}))
;; Running it later:
(prettify-bands input)
Github issue
!
Ths page is live and interactive powered by the klipse plugin: