A library for diffing, and patching, Clojure(script) datastructures.
This is a forked from robinheghan/differ to address some issues.
I wanted to implement an auto-save feature for my Clojurescript web-app. To be efficient, only the actual changes should be sent to the backend.
clojure.data/diff
is not the easiest function to work with for several reasons, and there didn't seem to be any good alternatives, so differ was born.
- @robinheghan Robin Heggelund Hansen
This fork is available by adding git coordinates in deps.edn:
:deps {io.github.banzai-inc/differ {:git/tag "v0.4.0" :git/sha "08e964af0ccfc2149a4ba98de1498cb640d6af0c"}}
First of all, you need to require the proper namespace:
(ns some.ns
(:require [differ.core :as differ]))
You can create a diff using the differ.core/diff
function:
(def person-map {:name "Robin"
:age 25
:gender :male
:phone {:home 99999999
:work 12121212})
(def person-diff (differ/diff person-map {:name "Robin Heggelund Hansen"
:age 26
:phone {:home 99999999})
;; person-diff will now be [{:name "Robin Heggelund Hansen"
;; :age 26}
;; {:gender 0
;; :phone {:work 0}]
differ.core/diff
will return a data structure of the same type that is given, and will work with nested data structures. If you only want alterations, or removals, instead of both, please check the differ.diff
and differ.patch
namespaces.
To apply the diff, you can use the differ.core/patch
function. This function works on any similar data structure:
(differ/patch {:species :human
:gender :female}
person-diff)
;; Will return {:name "Robin Heggelund Hansen"
;; :age 26
;; :species :human}
Maps are probably the best supported, and most straight forward type to diff. Alterations are a simple map of the key-value pairs missing. Removals are a map of keys where the value is 0, or a nested data structure. Check the "Usage" section for a decent example.
Differ works by checking what values have changed for a given key. For sequential types (vectors, lists and seqs) this means that alterations is represented as a sequential type of [index diff]
for every key that has a changed value. This unfortunetly means that differ does not detect if elements have simply changed places.
Removals are represented as a sequential type containing number of elements to drop from the end of the sequence, and [index diff]
for every nested type that contains removals (everything else is an alteration).
Differ does diff between sequential types, but remains the correct type of the new state.
(ns test
(:require [differ.diff :as diff]))
(diff/alterations '(1 2 3) [1 2 2 4])
;; [2 2 :+ 4]
(diff/removals [1 {:a 2} 3] '(1 {}))
;; (1 1 {:a 0})
Because differ works by checking if the value for a given key has changed, sets does not support nesting (every element is it's own key). Differ can therefore only detect if elements have been added or removed from a set, and not if they have changed. If you have sets in your datastructure, you should keep them shallow to avoid a large diff.
(ns test
(:require [differ.diff :as diff]))
(diff/alterations #{1 2 3} #{1 2 3 4})
;; #{4}
(diff/removals #{1 {:a 2} 3} #{{} 1})
;; #{{:a 2} 3} <-- does not pick up changes
Feedback to both this library and this guide is welcome. Plese read CONTRIBUTING.md
for more information.
Differ is assumed to work with Clojure 1.8 and up, as well as a recent Clojurescript version.
There is a leiningen alias that makes it easy to run the tests against supported Clojure versions:
> lein all-tests
Copyright © 2014-2019 Robin Heggelund Hansen.
Distributed under the MIT License.