Clojure data structure manipulation and querying library
Clojure
Latest commit d0bdcf4 Jan 2, 2016 @michaelklishin michaelklishin Update (c) year

README.md

Balagan, Clojure (Script) data transformation and querying library

A tiny library for data structure transformation inspired by Enlive.

It's often hard to come up with a sensible DSL for data structure transformation. You either get too close to the data domain, and your functions get messy and difficult to write (or even maybe too nested) or you're too far away from data, and your functions operate nested structures as if they were plain.

Bałagan solves that problem for you by providing you with predicate-based selectors and arbitrary transformation functions that suits any taste. It's mostly like get-in and update-in on steroids, that allows you to build predicate queries, not only concrete ones.

Project Maturity

Balagan is a fairly young project, but it's been used in production for around a year by now, and has proven itself to be safe and stable.

Artifacts

Bałagan artifacts are released to Clojars. If you are using Maven, add the following repository definition to your pom.xml:

<repository>
  <id>clojars.org</id>
  <url>http://clojars.org/repo</url>
</repository>

Most recent release version

With Leiningen:

[clojurewerkz/balagan "1.0.5"]

With Maven:

<dependency>
  <groupId>clojurewerkz</groupId>
  <artifactId>balagan</artifactId>
  <version>1.0.5</version>
</dependency>

Documentation and Examples

Balagan builds on ideas from Enlive. It may help to get familiar with them first.

Let's say you're working on a Data Access layer for the application. You have a user entry represented as hash:

(def user
  {:name "Alex"
   :birth-year 1990
   :nickname "ifesdjeen"})

Transformation

Now, we can start transforming users the way we want: add, remove fields based on certain conditions.

(update user
           []                  (add-field :cool-dude true) ;; adds a field :cool-dude with value true
           (mk-path [:age])   #(- 2014 (:birth-year %))   ;; explicit adding of a new field, calculated from the existing data
           (mk-path [:posts]) #(fetch-posts (:name %))    ;; fetching some related data from the DB
           [:posts :*]         #(update-posts %))       ;; apply some transformations to all the fetched posts, if there are any

Queries

Queries are very similar to how you'd query your data with filter in Clojure:

(let [data {:a {:b [{:c 1} {:c 2} {:c 3}]
                :d [{:c 5} {:c 6} {:c 7}]}}]
  (select data  [:* :* even? :c]))
;; => (1 3 5 7)  

Results are returned in the order they've been seen in your data structure, however you should be aware of the fact that iterating over the hash in Clojure doesn't guarantee you order.

Path Queries

Path queries are most useful when you'd like to fire a function against some part of your data (be it processing, database initialization or anything else.

You can also run predicate queries based on your map, for example if you want to configure your database servers from rather big and complex config:

(def conf {:db
           {:redis {:cache  [{:host "host01" :port 1234} {:host "host02" :port 1234}]
                    :pubsub [{:host "host01" :port 1234} {:host "host02" :port 1234}]}}
           :cassandra [{:host "host01"} {:host "host02"}]})

(with-paths conf
        [:db :redis :cache]  configure-redis-cache
        [:db :redis :pubsub] configure-redis-pubsub
        [:db :cassandra]     configure-cassandra)

In this example, configure-redis-cache funciton will receive two arguments: value and path:

(defn configure-redis-cache
  [value path]
  (println "Value: " value)
  (println "Path: " path))

;; => Value: [{:host host01, :port 1234} {:host host02, :port 1234}]
;; => Path: [:db :redis :cache]

You can also do wildcard-matching with :*, for example:

(b/select {:a {:b {:c 1} :d {:c 2}}}
          [:a :* :c] (fn [val path]
                       (if (= path [:a :b :c])
                         (is (= val 1))
                         (is (= val 2)))))

Community

To subscribe for announcements of releases, important changes and so on, please follow @ClojureWerkz on Twitter.

Balagan Is a ClojureWerkz Project

Balagan is part of the group of libraries known as ClojureWerkz, together with Monger, Welle, Neocons, Elastisch and several others.

Continuous Integration

Continuous Integration status

CI is hosted by travis-ci.org

Development

Balagan uses Leiningen 2. Make sure you have it installed and then run tests against all supported Clojure versions using

lein do clean, cljx once, all test, cljsbuild test

Then create a branch and make your changes on it. Once you are done with your changes and all tests pass, submit a pull request on Github.

Balagan?

Hash is a synonym of mess, Bałagan means mess in several Slavic languages.

License

Copyright © 2014-2016 Alex P, Michael S. Klishin, and the ClojureWerkz team.

Distributed under the Eclipse Public License, the same as Clojure.