From 46bc86498a60f008be1c3e50a4804fdfdb5527df Mon Sep 17 00:00:00 2001 From: Pyry Koivisto Date: Thu, 23 Nov 2023 15:28:50 +0200 Subject: [PATCH 1/6] Ignore IntelliJ IDEA artifacts --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e04714b..4e55b20 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ pom.xml.asc *.class /.lein-* /.nrepl-port +.idea +*.iml From 831915976590dbc2c5fc1009837b63a906a01112 Mon Sep 17 00:00:00 2001 From: Pyry Koivisto Date: Thu, 23 Nov 2023 15:29:17 +0200 Subject: [PATCH 2/6] Add failing unit test --- test/clj_json_patch/core_test.clj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/clj_json_patch/core_test.clj b/test/clj_json_patch/core_test.clj index 78c2f14..ffd18ae 100644 --- a/test/clj_json_patch/core_test.clj +++ b/test/clj_json_patch/core_test.clj @@ -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"}]] From 48f3326fd95041e6df355b85bade2c3a77c75baf Mon Sep 17 00:00:00 2001 From: Pyry Koivisto Date: Thu, 23 Nov 2023 15:31:17 +0200 Subject: [PATCH 3/6] Ensure multiple additions or removals to vector produce a truthful diff. Also improve performance of diff-vecs for the likely frequent cases of vectors being exactly same or sharing long prefixes or suffixes. --- src/clj_json_patch/util.cljc | 93 +++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/src/clj_json_patch/util.cljc b/src/clj_json_patch/util.cljc index d024b2e..ed07c19 100644 --- a/src/clj_json_patch/util.cljc +++ b/src/clj_json_patch/util.cljc @@ -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." From c0df75a599140bc96da7f5e32cad96cac5f7a0c6 Mon Sep 17 00:00:00 2001 From: Pyry Koivisto Date: Thu, 23 Nov 2023 15:31:50 +0200 Subject: [PATCH 4/6] Update project version in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12bca1e..8455592 100644 --- a/README.md +++ b/README.md @@ -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 From ad403f64efa0f8e7b3a35ac4e7e55f4344cf73f7 Mon Sep 17 00:00:00 2001 From: Pyry Koivisto Date: Thu, 23 Nov 2023 15:32:50 +0200 Subject: [PATCH 5/6] Upgrade dependencies --- project.clj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/project.clj b/project.clj index 0d020db..5c40b5b 100644 --- a/project.clj +++ b/project.clj @@ -4,9 +4,9 @@ :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"]]}}) From c453384e45b6f20c50f3fe79bde908ef174d5437 Mon Sep 17 00:00:00 2001 From: Pyry Koivisto Date: Thu, 23 Nov 2023 15:33:31 +0200 Subject: [PATCH 6/6] Bump artifact version --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 5c40b5b..58f3f77 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(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"