Skip to content

Commit 238ad5b

Browse files
committed
Add myers diff for handling arrays
1 parent 425d414 commit 238ad5b

File tree

3 files changed

+84
-40
lines changed

3 files changed

+84
-40
lines changed

Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[package]
22
name = "json_diff_ng"
3-
version = "0.3.0-rc1"
3+
version = "0.3.0"
44
authors = ["ksceriath", "ChrisRega"]
55
edition = "2021"
66
license = "Unlicense"
7-
description = "A small diff tool utility for comparing jsons. Forked from ksceriath but improved for usage as a library."
7+
description = "A small diff tool utility for comparing jsons. Forked from ksceriath and improved for usage as a library and with good support for array diffs."
88
readme = "README.md"
99
homepage = "https://github.com/ChrisRega/json-diff"
1010
repository = "https://github.com/ChrisRega/json-diff"
@@ -28,3 +28,4 @@ vg_errortools = "0.1"
2828
serde_json = "1.0"
2929
maplit = "1.0"
3030
clap = {version = "4.4", features = ["derive"]}
31+
diffs = "0.5"

README.md

-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ Input can be fed as inline strings or through files.
66
For readability, output is neatly differentiated into three categories: keys with different values, and keys not present in either of the objects.
77
Only missing or unequal keys are printed in output to reduce the verbosity.
88

9-
## Screenshot of diff results
10-
11-
[![A screenshot of a sample diff with json_diff](https://github.com/ksceriath/json-diff/blob/master/Screenshot.png)](https://github.com/ksceriath/json-diff/blob/master/Screenshot.png)
12-
139
Usage Example:
1410

1511
`$ json_diff file source1.json source2.json`

src/process.rs

+81-34
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use diffs::{myers, Diff, Replace};
12
use std::collections::HashMap;
23
use std::collections::HashSet;
34

@@ -25,6 +26,40 @@ fn values_to_node(vec: Vec<(usize, &Value)>) -> KeyNode {
2526
}
2627
}
2728

29+
struct ListDiffHandler<'a> {
30+
replaced: &'a mut Vec<(usize, usize, usize, usize)>,
31+
deletion: &'a mut Vec<(usize, usize)>,
32+
insertion: &'a mut Vec<(usize, usize)>,
33+
}
34+
impl<'a> ListDiffHandler<'a> {
35+
pub fn new(
36+
replaced: &'a mut Vec<(usize, usize, usize, usize)>,
37+
deletion: &'a mut Vec<(usize, usize)>,
38+
insertion: &'a mut Vec<(usize, usize)>,
39+
) -> Self {
40+
Self {
41+
replaced,
42+
deletion,
43+
insertion,
44+
}
45+
}
46+
}
47+
impl<'a> Diff for ListDiffHandler<'a> {
48+
type Error = ();
49+
fn delete(&mut self, old: usize, len: usize, _new: usize) -> Result<(), ()> {
50+
self.deletion.push((old, len));
51+
Ok(())
52+
}
53+
fn insert(&mut self, _o: usize, new: usize, len: usize) -> Result<(), ()> {
54+
self.insertion.push((new, len));
55+
Ok(())
56+
}
57+
fn replace(&mut self, old: usize, len: usize, new: usize, new_len: usize) -> Result<(), ()> {
58+
self.replaced.push((old, len, new, new_len));
59+
Ok(())
60+
}
61+
}
62+
2863
pub fn match_json(value1: &Value, value2: &Value) -> Mismatch {
2964
match (value1, value2) {
3065
(Value::Object(a), Value::Object(b)) => {
@@ -51,40 +86,33 @@ pub fn match_json(value1: &Value, value2: &Value) -> Mismatch {
5186
}
5287
// this clearly needs to be improved! myers algorithm or whatever?
5388
(Value::Array(a), Value::Array(b)) => {
54-
let mut mismatch = Vec::new();
55-
let mut left_only_values = Vec::new();
56-
let mut right_only_values = Vec::new();
57-
58-
let max_len = a.len().max(b.len());
59-
for idx in 0..max_len {
60-
let a = a.get(idx);
61-
let b = b.get(idx);
62-
match (a, b) {
63-
(Some(a), Some(b)) => {
64-
if a != b {
65-
let Mismatch {
66-
left_only_keys: l,
67-
right_only_keys: r,
68-
keys_in_both: u,
69-
} = match_json(a, b);
70-
for node in [l, r, u] {
71-
if !matches!(node, KeyNode::Nil) {
72-
mismatch.push((idx, node));
73-
}
74-
}
75-
}
76-
}
77-
(Some(a), None) => {
78-
left_only_values.push((idx, a));
79-
}
80-
(None, Some(b)) => {
81-
right_only_values.push((idx, b));
82-
}
83-
(None, None) => {
84-
unreachable!();
85-
}
86-
}
87-
}
89+
let mut replaced = Vec::new();
90+
let mut deleted = Vec::new();
91+
let mut inserted = Vec::new();
92+
93+
let mut diff = Replace::new(ListDiffHandler::new(
94+
&mut replaced,
95+
&mut deleted,
96+
&mut inserted,
97+
));
98+
myers::diff(&mut diff, a, 0, a.len(), b, 0, b.len()).unwrap();
99+
100+
let mismatch: Vec<_> = replaced
101+
.into_iter()
102+
.flat_map(|(o, ol, n, _nl)| {
103+
(0..ol).map(move |i| (o + i, match_json(&a[o + i], &b[n + i]).keys_in_both))
104+
})
105+
.collect();
106+
107+
let left_only_values: Vec<_> = deleted
108+
.into_iter()
109+
.flat_map(|(o, ol)| (o..o + ol).map(|i| (i, &a[i])))
110+
.collect();
111+
112+
let right_only_values: Vec<_> = inserted
113+
.into_iter()
114+
.flat_map(|(n, nl)| (n..n + nl).map(|i| (i, &b[i])))
115+
.collect();
88116

89117
let left_only_nodes = values_to_node(left_only_values);
90118
let right_only_nodes = values_to_node(right_only_values);
@@ -204,6 +232,25 @@ mod tests {
204232
);
205233
}
206234

235+
#[test]
236+
fn test_arrays_more_complex_diff() {
237+
let data1 = r#"["a","b","c"]"#;
238+
let data2 = r#"["a","a","b","d"]"#;
239+
let diff = compare_jsons(data1, data2).unwrap();
240+
241+
let changes_diff = diff.keys_in_both.absolute_keys_to_vec(None);
242+
assert_eq!(diff.left_only_keys, KeyNode::Nil);
243+
244+
assert_eq!(changes_diff.len(), 1);
245+
assert_eq!(
246+
changes_diff.first().unwrap().to_string(),
247+
r#"[l: 2] -> { "c" != "d" }"#
248+
);
249+
let insertions = diff.right_only_keys.absolute_keys_to_vec(None);
250+
assert_eq!(insertions.len(), 1);
251+
assert_eq!(insertions.first().unwrap().to_string(), r#" [l: 0] - "a""#);
252+
}
253+
207254
#[test]
208255
fn test_arrays_extra_left() {
209256
let data1 = r#"["a","b","c"]"#;

0 commit comments

Comments
 (0)