Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ pom.xml.asc
*.class
/.lein-*
/.nrepl-port
.idea
*.iml
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ JSON Pointer https://tools.ietf.org/html/rfc6901
Usage
-----
```clojure
[clj-json-patch 0.1.7]
[clj-json-patch 0.1.8]

;; From some example namespace:
(ns example.namespace
Expand Down
10 changes: 5 additions & 5 deletions project.clj
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
(defproject clj-json-patch "0.1.7"
(defproject clj-json-patch "0.1.8"
:description "Clojure implementation of http://tools.ietf.org/html/rfc6902"
:url "http://github.com/daviddpark/clj-json-patch"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:main clj-json-patch.core
:dependencies [[org.clojure/clojure "1.10.0"]
[cheshire "5.8.0"]]
:dependencies [[org.clojure/clojure "1.11.1"]
[cheshire "5.12.0"]]
:deploy-repositories [["releases" :clojars]
["snapshots" :clojars]]
:profiles {:dev {:dependencies [[midje/midje "1.9.5"]]
:plugins [[lein-midje "3.2.1"]]}})
:profiles {:dev {:dependencies [[midje/midje "1.10.9"]]
:plugins [[lein-midje "3.2.1"]]}})
93 changes: 60 additions & 33 deletions src/clj_json_patch/util.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -369,40 +369,67 @@
(sanitize prefix patch)))

(defn diff-vecs [obj1 obj2 prefix]
(loop [v1 obj1
v2 obj2
i 0
(loop [v1 obj1
v2 obj2
i 0
ops []]
(cond (and (empty? v1) (empty? v2))
ops
(and (> (count ops) 0)
(= v2
(reduce
#(apply-patch %1 %2) v1
(map (partial sanitize-prefix-in-patch prefix (dec i)) ops))))
ops
(= (set v1) (set v2))
(cond (= i (count v1))
ops
(= (get v1 i) (get v2 i))
(recur v1 v2 (inc i) ops)
(not= (get v1 i) (get v2 i))
(let [moved-idx (first (filter (complement nil?) (map-indexed #(if (= (get v1 i) %2) %1) v2)))]
(recur v1 v2 (inc i)
(conj ops {"op" "move" "from" (str prefix i) "path" (str prefix moved-idx)}))))
(= v1 (rest v2))
(conj ops (gen-op ["add" (str prefix i) (first v2)]))
(= (rest v1) v2)
(conj ops (gen-op ["remove" (str prefix i)]))
(not= (first v1) (first v2))
(if (and (map? (first v1)) (map? (first v2)))
(recur (rest v1) (rest v2) (inc i)
(conj ops (diff* (first v1) (first v2) (str prefix i "/"))))
(recur (rest v1) (rest v2) (inc i)
(conj ops (gen-op ["replace" (str prefix i) (first v2)]))))
(and (= (first v1) (first v2))
(not= (rest v1) (rest v2)))
(recur (rest v1) (rest v2) (inc i) ops))))
(cond
; Performance optimization: if most diff'ed vectors are likely to contain long shared prefixes or suffixes,
; a straightforward equality comparison is going to be much faster than performing the
; reduction with apply-patch as given below.
(= v1 v2)
ops

; v1 is a prefix of v2 => append "add" to ops and recur
(or (empty? v1)
(= v1 (rest v2)))
(recur
v1
(rest v2)
(inc i)
(conj ops (gen-op ["add" (str prefix i) (first v2)])))

; v2 is a prefix of v1 => append "remove" to ops and recur
(or (empty? v2)
(= v2 (rest v1)))
(recur
(rest v1)
v2
; Note: intentionally keeping i same between iterations when removing element.
i
(conj ops (gen-op ["remove" (str prefix i)])))

; v2 equals v1+patches => return ops
(and (> (count ops) 0)
(= v2
(reduce
#(apply-patch %1 %2) v1
(map (partial sanitize-prefix-in-patch prefix (dec i)) ops))))
ops

; v1 and v2 contain the same items => need to possibly move objects
(= (set v1) (set v2))
(cond (= i (count v1))
ops
(= (get v1 i) (get v2 i))
(recur v1 v2 (inc i) ops)
(not= (get v1 i) (get v2 i))
(let [moved-idx (first (filter (complement nil?) (map-indexed #(if (= (get v1 i) %2) %1) v2)))]
(recur v1 v2 (inc i)
(conj ops {"op" "move" "from" (str prefix i) "path" (str prefix moved-idx)}))))

; Different first elements
(not= (first v1) (first v2))
(if (and (map? (first v1)) (map? (first v2)))
(recur (rest v1) (rest v2) (inc i)
(conj ops (diff* (first v1) (first v2) (str prefix i "/"))))
(recur (rest v1) (rest v2) (inc i)
(conj ops (gen-op ["replace" (str prefix i) (first v2)]))))

; Same first elements, different suffixes
(and (= (first v1) (first v2))
(not= (rest v1) (rest v2)))
(recur (rest v1) (rest v2) (inc i) ops))))

(defn get-value-path
"Traverses obj, looking for a value that matches val, returns path to value."
Expand Down
6 changes: 6 additions & 0 deletions test/clj_json_patch/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
patches [{"op" "add" "path" "/foo/0" "value" "qux"}]]
(fact "Adding an Array Element"
(diff obj1 obj2) => patches))
(let [obj1 {"foo" []}
obj2 {"foo" ["bar" "baz"]}
patches [{"op" "add" "path" "/foo/0" "value" "bar"}
{"op" "add" "path" "/foo/1" "value" "baz"}]]
(fact "Adding two Array Elements"
(diff obj1 obj2) => patches))
(let [obj1 {"foo" ["bar" "baz"]}
obj2 {"foo" ["bar" "qux" "baz"]}
patches [{"op" "add" "path" "/foo/1" "value" "qux"}]]
Expand Down