Compact, context-preserving diffs of structured data.
cool-diff compares two serde_json::Value trees and produces a minimal, human-readable diff. It is format-agnostic at the core (operates on parsed JSON values) and ships with a YAML-style renderer out of the box.
Designed for comparing Kubernetes resources, API responses, config files, or any structured data where you want to see exactly what changed without wading through noise.
- Format-agnostic diff on
serde_json::Valuetrees - Array matching by index, distinguished key, or content (contains)
- Configurable per-path array matching and ambiguity strategies
- Custom renderer support via
DiffRenderertrait - YAML-style renderer with unified diff output (
-/+indicators)- Truncation for large subtrees (
# N more lines) - Omitted field/item markers (
# N fields omitted) - ANSI colour output for terminal rendering (behind
colorfeature gate)
- Truncation for large subtrees (
- JSON renderer
- Pre-configured
MatchConfigfor common Kubernetes resource types - Inline comparison directives in the expected value
spec:
# 2 fields omitted
containers:
- name: app
# 2 fields omitted
env:
- name: LOG_LEVEL
- value: debug
+ value: info
- image: "myapp:2.0"
+ image: "myapp:1.0"Add to your Cargo.toml:
[dependencies]
cool-diff = "0.1"use cool_diff::{diff, DiffConfig, DiffRenderer as _, YamlRenderer};
let actual = serde_json::json!({
"server": {
"host": "0.0.0.0",
"port": 8080,
"tls": true,
}
});
let expected = serde_json::json!({
"server": {
"port": 3000,
}
});
let tree = diff(&actual, &expected, &DiffConfig::default());
if !tree.is_empty() {
let output = YamlRenderer::new().render(&tree);
print!("{output}");
}Output:
server:
# 2 fields omitted
- port: 3000
+ port: 8080By default, arrays are compared by position (index). You can configure per-path matching via MatchConfig:
| Mode | Description |
|---|---|
| Index (default) | Match by position. Element 0 compares to element 0, etc. |
| Key | Match by a configured distinguished field (e.g. name). Scans the actual array for an element with the same key value. |
| Contains | Find a matching element anywhere. Uses exact comparison for scalars, subset matching for objects. |
use cool_diff::{ArrayMatchConfig, ArrayMatchMode, DiffConfig, MatchConfig};
let config = DiffConfig::new().with_match_config(
MatchConfig::new()
.with_config_at(
"spec.containers",
ArrayMatchConfig::new(ArrayMatchMode::Key("name".to_owned())),
)
.with_config_at(
"tags",
ArrayMatchConfig::new(ArrayMatchMode::Contains),
),
);The built-in YamlRenderer produces diff output using unified diff conventions:
Every line starts with an indicator in column 0:
(space) for context lines-for expected values (what you wanted)+for actual values (what you got)
The renderer is configurable:
use cool_diff::YamlRenderer;
let renderer = YamlRenderer::new()
.with_max_lines_per_side(Some(10)) // truncate large subtrees
.with_indent_width(4); // custom indentationYou can also implement the DiffRenderer trait for custom output formats. See examples/custom_renderer.rs.
Runnable examples are in the examples/ directory:
- barebones - simplest usage with all defaults
- match_configs - mix of index, key, and contains matching
- contains_check - checking that elements exist regardless of order
- kubernetes - diffing a Kubernetes Pod with key-based array matching
- custom_renderer - implementing a custom
DiffRenderer
Run any example with:
cargo run --example barebonesTip
Add --features color for coloured output:
cargo run --example barebones --features colorSee CHANGELOG.md for a list of changes per release.
Contributions are welcome! A contributing guide is coming soon.
Important
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you shall be dual licensed under MIT and Apache-2.0, without any additional terms or conditions.
See RELEASE.md for the release process and commit conventions.
Licensed under either of
at your option.