From 7073fd23587bea9cf12238cb00d4e2f3dd2167e0 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Thu, 23 May 2024 00:08:42 +0200 Subject: [PATCH] - Clean up truncation - Write better return type - Improve array return type support - Improve visual representation of diffs (more jq-like) --- Cargo.toml | 4 +-- src/ds/key_node.rs | 81 ++++++++++++++++++++-------------------------- src/ds/mismatch.rs | 15 ++++----- src/enums.rs | 58 ++++++++++++++++----------------- src/process.rs | 75 +++++++++++++++++++++++++----------------- 5 files changed, 116 insertions(+), 117 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5c3d9e6..d953f26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "json_diff_ng" -version = "0.5.0" +version = "0.6.0-RC1" authors = ["ksceriath", "ChrisRega"] edition = "2021" license = "Unlicense" -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." +description = "A small diff tool utility for comparing jsons. Forked from ksceriath and improved for usage as a library and with proper support for array diffs." readme = "README.md" homepage = "https://github.com/ChrisRega/json-diff" repository = "https://github.com/ChrisRega/json-diff" diff --git a/src/ds/key_node.rs b/src/ds/key_node.rs index 4c1d69f..41bd766 100644 --- a/src/ds/key_node.rs +++ b/src/ds/key_node.rs @@ -1,65 +1,54 @@ -use crate::enums::ValueType; -use serde_json::Value; use std::collections::HashMap; +use serde_json::Value; + +use crate::enums::{DiffEntry, PathElement}; + #[derive(Debug, PartialEq)] pub enum KeyNode { Nil, Value(Value, Value), Node(HashMap), -} - -fn truncate(s: &str, max_chars: usize) -> String { - match s.char_indices().nth(max_chars) { - None => String::from(s), - Some((idx, _)) => { - let shorter = &s[..idx]; - let snip = "//SNIP//"; - let new_s = format!("{}{}", shorter, snip); - new_s - } - } + Array(Vec<(usize, KeyNode)>), } impl KeyNode { - pub fn absolute_keys_to_vec(&self, max_display_length: Option) -> Vec { - let mut vec = Vec::new(); - self.absolute_keys(&mut vec, None, max_display_length); - vec + pub fn get_diffs(&self) -> Vec { + let mut buf = Vec::new(); + self.follow_path(&mut buf, &[]); + buf } - pub fn absolute_keys( - &self, - keys: &mut Vec, - key_from_root: Option, - max_display_length: Option, - ) { - let max_display_length = max_display_length.unwrap_or(4000); - let val_key = |key: Option| { - key.map(|mut s| { - s.push_str("->"); - s - }) - .unwrap_or_default() - }; + pub fn follow_path(&self, diffs: &mut Vec, offset: &[PathElement]) { match self { KeyNode::Nil => { - if let Some(key) = key_from_root { - keys.push(ValueType::new_key(key)) + let is_map_child = offset + .last() + .map(|o| matches!(o, PathElement::Object(_))) + .unwrap_or_default(); + if is_map_child { + diffs.push(DiffEntry { + path: offset.to_vec(), + values: None, + }); + } + } + KeyNode::Value(l, r) => diffs.push(DiffEntry { + path: offset.to_vec(), + values: Some((l.to_string(), r.to_string())), + }), + KeyNode::Node(o) => { + for (k, v) in o { + let mut new_offset = offset.to_vec(); + new_offset.push(PathElement::Object(k.clone())); + v.follow_path(diffs, &new_offset); } } - KeyNode::Value(a, b) => keys.push(ValueType::new_value( - val_key(key_from_root), - truncate(a.to_string().as_str(), max_display_length), - truncate(b.to_string().as_str(), max_display_length), - )), - KeyNode::Node(map) => { - for (key, value) in map { - value.absolute_keys( - keys, - Some(format!("{}{}", val_key(key_from_root.clone()), key)), - Some(max_display_length), - ) + KeyNode::Array(v) => { + for (l, k) in v { + let mut new_offset = offset.to_vec(); + new_offset.push(PathElement::ArrayEntry(*l)); + k.follow_path(diffs, &new_offset); } } } diff --git a/src/ds/mismatch.rs b/src/ds/mismatch.rs index e3f3376..92f2ff4 100644 --- a/src/ds/mismatch.rs +++ b/src/ds/mismatch.rs @@ -1,5 +1,5 @@ use crate::ds::key_node::KeyNode; -use crate::enums::{DiffType, ValueType}; +use crate::enums::{DiffEntry, DiffType}; #[derive(Debug, PartialEq)] pub struct Mismatch { @@ -31,24 +31,20 @@ impl Mismatch { && self.right_only_keys == KeyNode::Nil } - pub fn all_diffs(&self) -> Vec<(DiffType, ValueType)> { - self.all_diffs_trunc(None) - } - - pub fn all_diffs_trunc(&self, truncation_length: Option) -> Vec<(DiffType, ValueType)> { + pub fn all_diffs(&self) -> Vec<(DiffType, DiffEntry)> { let both = self .keys_in_both - .absolute_keys_to_vec(truncation_length) + .get_diffs() .into_iter() .map(|k| (DiffType::Mismatch, k)); let left = self .left_only_keys - .absolute_keys_to_vec(truncation_length) + .get_diffs() .into_iter() .map(|k| (DiffType::LeftExtra, k)); let right = self .right_only_keys - .absolute_keys_to_vec(truncation_length) + .get_diffs() .into_iter() .map(|k| (DiffType::RightExtra, k)); @@ -59,6 +55,7 @@ impl Mismatch { #[cfg(test)] mod test { use super::*; + #[test] fn empty_diffs() { let empty = Mismatch::empty(); diff --git a/src/enums.rs b/src/enums.rs index c9d261e..6c7f51f 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -1,4 +1,5 @@ use std::fmt::{Display, Formatter}; + use thiserror::Error; use vg_errortools::FatIOError; @@ -30,45 +31,42 @@ impl Display for DiffType { } } -pub enum ValueType { - Key(String), - Value { - key: String, - value_left: String, - value_right: String, - }, +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PathElement { + Object(String), + ArrayEntry(usize), } -impl ValueType { - pub fn new_value(key: String, value_left: String, value_right: String) -> Self { - Self::Value { - value_right, - value_left, - key, - } - } - pub fn new_key(key: String) -> Self { - Self::Key(key) - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct DiffEntry { + pub path: Vec, + pub values: Option<(String, String)>, +} - pub fn get_key(&self) -> &str { - match self { - ValueType::Value { key, .. } => key.as_str(), - ValueType::Key(key) => key.as_str(), +impl Display for DiffEntry { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for element in &self.path { + write!(f, ".{element}")?; } + if let Some((l, r)) = &self.values { + if l != r { + write!(f, ".({l} != {r})")?; + } else { + write!(f, ".({l})")?; + } + } + Ok(()) } } -impl Display for ValueType { +impl Display for PathElement { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - ValueType::Key(key) => write!(f, "{key}"), - ValueType::Value { - value_left, - key, - value_right, - } => { - write!(f, "{key}{{{value_left}!={value_right}}}") + PathElement::Object(o) => { + write!(f, "{o}") + } + PathElement::ArrayEntry(l) => { + write!(f, "[{l}]") } } } diff --git a/src/process.rs b/src/process.rs index 76f3319..a6663b4 100644 --- a/src/process.rs +++ b/src/process.rs @@ -25,9 +25,9 @@ fn values_to_node(vec: Vec<(usize, &Value)>) -> KeyNode { if vec.is_empty() { KeyNode::Nil } else { - KeyNode::Node( + KeyNode::Array( vec.into_iter() - .map(|(id, val)| (format!("[l: {id}]-{}", val), KeyNode::Nil)) + .map(|(l, v)| (l, KeyNode::Value(v.clone(), v.clone()))) .collect(), ) } @@ -153,11 +153,9 @@ pub fn match_json( right_only_keys: r, keys_in_both: u, } = cdiff; - left_only_nodes = - insert_child_key_map(left_only_nodes, l, &format!("[l: {position}]")); - right_only_nodes = - insert_child_key_map(right_only_nodes, r, &format!("[l: {position}]")); - diff = insert_child_key_map(diff, u, &format!("[l: {position}]")); + left_only_nodes = insert_child_key_diff(left_only_nodes, l, position); + right_only_nodes = insert_child_key_diff(right_only_nodes, r, position); + diff = insert_child_key_diff(diff, u, position); } } @@ -269,13 +267,27 @@ fn get_map_of_keys(set: HashSet) -> KeyNode { } } +fn insert_child_key_diff(parent: KeyNode, child: KeyNode, line: usize) -> KeyNode { + if child == KeyNode::Nil { + return parent; + } + if let KeyNode::Array(mut array) = parent { + array.push((line, child)); + KeyNode::Array(array) + } else if let KeyNode::Nil = parent { + KeyNode::Array(vec![(line, child)]) + } else { + parent // TODO Trying to insert child node in a Value variant : Should not happen => Throw an error instead. + } +} + fn insert_child_key_map(parent: KeyNode, child: KeyNode, key: &String) -> KeyNode { if child == KeyNode::Nil { return parent; } if let KeyNode::Node(mut map) = parent { map.insert(String::from(key), child); - KeyNode::Node(map) // This is weird! I just wanted to return back `parent` here + KeyNode::Node(map) } else if let KeyNode::Nil = parent { let mut map = HashMap::new(); map.insert(String::from(key), child); @@ -424,9 +436,9 @@ mod tests { let data2 = r#"["b","c",{"c": ["e", "d"] }]"#; let diff = compare_jsons(data1, data2, true, &[]).unwrap(); assert!(!diff.is_empty()); - let insertions = diff.right_only_keys.absolute_keys_to_vec(None); + let insertions = diff.right_only_keys.get_diffs(); assert_eq!(insertions.len(), 1); - assert_eq!(insertions.first().unwrap().to_string(), r#"[l: 2]-"c""#); + assert_eq!(insertions.first().unwrap().to_string(), r#".[2].("c")"#); } #[test] @@ -435,12 +447,12 @@ mod tests { let data2 = r#"["b",{"c": ["e","d"] },"a"]"#; let diff = compare_jsons(data1, data2, true, &[]).unwrap(); assert!(!diff.is_empty()); - let deletions = diff.left_only_keys.absolute_keys_to_vec(None); + let deletions = diff.left_only_keys.get_diffs(); assert_eq!(deletions.len(), 1); assert_eq!( deletions.first().unwrap().to_string(), - r#"[l: 0]->c->[l: 2]-"f""# + r#".[0].c.[2].("f")"# ); } @@ -450,12 +462,12 @@ mod tests { let data2 = r#"["b",{"c": ["e","d"] },"a"]"#; let diff = compare_jsons(data1, data2, true, &[]).unwrap(); assert!(!diff.is_empty()); - let diffs = diff.keys_in_both.absolute_keys_to_vec(None); + let diffs = diff.keys_in_both.get_diffs(); assert_eq!(diffs.len(), 1); assert_eq!( diffs.first().unwrap().to_string(), - r#"[l: 0]->c->[l: 1]->{"f"!="e"}"# + r#".[0].c.[1].("f" != "e")"# ); } @@ -466,9 +478,9 @@ mod tests { let diff = compare_jsons(data1, data2, false, &[]).unwrap(); assert_eq!(diff.left_only_keys, KeyNode::Nil); assert_eq!(diff.right_only_keys, KeyNode::Nil); - let diff = diff.keys_in_both.absolute_keys_to_vec(None); + let diff = diff.keys_in_both.get_diffs(); assert_eq!(diff.len(), 1); - assert_eq!(diff.first().unwrap().to_string(), r#"[l: 2]->{"c"!="d"}"#); + assert_eq!(diff.first().unwrap().to_string(), r#".[2].("c" != "d")"#); } #[test] @@ -477,17 +489,17 @@ mod tests { let data2 = r#"["a","a","b","d"]"#; let diff = compare_jsons(data1, data2, false, &[]).unwrap(); - let changes_diff = diff.keys_in_both.absolute_keys_to_vec(None); + let changes_diff = diff.keys_in_both.get_diffs(); assert_eq!(diff.left_only_keys, KeyNode::Nil); assert_eq!(changes_diff.len(), 1); assert_eq!( changes_diff.first().unwrap().to_string(), - r#"[l: 2]->{"c"!="d"}"# + r#".[2].("c" != "d")"# ); - let insertions = diff.right_only_keys.absolute_keys_to_vec(None); + let insertions = diff.right_only_keys.get_diffs(); assert_eq!(insertions.len(), 1); - assert_eq!(insertions.first().unwrap().to_string(), r#"[l: 0]-"a""#); + assert_eq!(insertions.first().unwrap().to_string(), r#".[0].("a")"#); } #[test] @@ -496,9 +508,9 @@ mod tests { let data2 = r#"["a","b"]"#; let diff = compare_jsons(data1, data2, false, &[]).unwrap(); - let diffs = diff.left_only_keys.absolute_keys_to_vec(None); + let diffs = diff.left_only_keys.get_diffs(); assert_eq!(diffs.len(), 1); - assert_eq!(diffs.first().unwrap().to_string(), r#"[l: 2]-"c""#); + assert_eq!(diffs.first().unwrap().to_string(), r#".[2].("c")"#); assert_eq!(diff.keys_in_both, KeyNode::Nil); assert_eq!(diff.right_only_keys, KeyNode::Nil); } @@ -509,9 +521,9 @@ mod tests { let data2 = r#"["a","b","c"]"#; let diff = compare_jsons(data1, data2, false, &[]).unwrap(); - let diffs = diff.right_only_keys.absolute_keys_to_vec(None); + let diffs = diff.right_only_keys.get_diffs(); assert_eq!(diffs.len(), 1); - assert_eq!(diffs.first().unwrap().to_string(), r#"[l: 2]-"c""#); + assert_eq!(diffs.first().unwrap().to_string(), r#".[2].("c")"#); assert_eq!(diff.keys_in_both, KeyNode::Nil); assert_eq!(diff.left_only_keys, KeyNode::Nil); } @@ -521,13 +533,16 @@ mod tests { let data1 = r#"["a","b","a"]"#; let data2 = r#"["a","c","c","c","a"]"#; let diff = compare_jsons(data1, data2, false, &[]).unwrap(); - let diffs = diff.keys_in_both.absolute_keys_to_vec(None); + let diffs = diff.keys_in_both.get_diffs(); assert_eq!(diffs.len(), 3); let diffs: Vec<_> = diffs.into_iter().map(|d| d.to_string()).collect(); - assert!(diffs.contains(&r#"[l: 3]->{null!="c"}"#.to_string())); - assert!(diffs.contains(&r#"[l: 1]->{"b"!="c"}"#.to_string())); - assert!(diffs.contains(&r#"[l: 2]->{"a"!="c"}"#.to_string())); + for diff in &diffs { + eprintln!("{diff}"); + } + assert!(diffs.contains(&r#".[3].(null != "c")"#.to_string())); + assert!(diffs.contains(&r#".[1].("b" != "c")"#.to_string())); + assert!(diffs.contains(&r#".[2].("a" != "c")"#.to_string())); assert_eq!(diff.right_only_keys, KeyNode::Nil); assert_eq!(diff.left_only_keys, KeyNode::Nil); } @@ -538,11 +553,11 @@ mod tests { let data2 = r#"["a","b", {"c": {"d": "e"} }]"#; let diff = compare_jsons(data1, data2, false, &[]).unwrap(); - let diffs = diff.right_only_keys.absolute_keys_to_vec(None); + let diffs = diff.right_only_keys.get_diffs(); assert_eq!(diffs.len(), 1); assert_eq!( diffs.first().unwrap().to_string(), - r#"[l: 2]-{"c":{"d":"e"}}"# + r#".[2].({"c":{"d":"e"}})"# ); assert_eq!(diff.keys_in_both, KeyNode::Nil); assert_eq!(diff.left_only_keys, KeyNode::Nil);