diffo
Semantic diffing for Rust structs via serde.
Compare any two Rust values that implement Serialize and get a detailed, human-readable diff showing exactly what changed. Perfect for audit logs, API testing, config validation, and debugging.
Installation
[]
= "0.2"
Quick Start
use diff;
use Serialize;
let old = User ;
let new = User ;
let d = diff.unwrap;
println!;
Output:
name
- "Alice"
+ "Alice Smith"
email
- "alice@old.com"
+ "alice@new.com"
Common Use Cases
- Audit Logs - Track what changed in your entities over time
- API Testing - Compare expected vs actual API responses
- Config Validation - Detect configuration drift between environments
- Undo/Redo - Apply diffs to reconstruct previous states
- Database Migrations - Verify data transformations
- CI/CD - Catch unintended changes in generated files
Features
- 🚀 Zero boilerplate - works with any
Serializetype - 🎯 Path-based changes - precise paths like
user.roles[2].name - 🎨 Multiple formats - pretty-print, JSON, JSON Patch (RFC 6902), Markdown
- 🔒 Secret masking - hide sensitive fields like passwords
- 🔧 Configurable - float tolerance, depth limits, collection size caps
- 🧬 Multiple diff algorithms - choose between speed and accuracy
- 🎭 Custom comparators - define your own equality logic per path
- 🔄 Diff application - apply diffs to produce new values
- ⚡ Fast - optimized allocations and configurable algorithms
Examples
Working with Diffs
use diff;
use Serialize;
let old = User ;
let new = User ;
let d = diff.unwrap;
// Check what changed
if !d.is_empty
Applying Diffs (Undo/Redo)
use ;
use ;
let v1 = Config ;
let v2 = Config ;
// Create diff from v1 to v2
let forward = diff.unwrap;
// Apply forward diff: v1 + diff = v2
let result: Config = apply.unwrap;
assert_eq!;
// Create reverse diff for undo
let backward = diff.unwrap;
let undone: Config = apply.unwrap;
assert_eq!;
Masking Secrets
use ;
let config = new
.mask
.mask;
let diff = diff_with.unwrap;
Float Tolerance
use DiffConfig;
let config = new
.float_tolerance
.default_float_tolerance;
let diff = diff_with.unwrap;
Sequence Diff Algorithms
Choose the right algorithm for your use case:
use ;
let config = new
// IndexBased: O(n) simple comparison (default, backward compatible)
.default_sequence_algorithm
// Myers: O(ND) optimal edit distance
.sequence_algorithm
// Patience: O(N log N) human-intuitive, great for code/reorderings
.sequence_algorithm;
let diff = diff_with.unwrap;
Algorithm comparison:
- IndexBased (default): Simple index-by-index comparison. Fast and predictable. Best for simple changes.
- Myers: Finds minimal edit distance. Best when you need optimal/shortest diff.
- Patience: Uses unique elements as anchors. Best for code diffs and when order changes significantly.
Custom Comparators
Define custom comparison logic for specific paths:
use ;
use Rc;
let config = new
// Ignore timestamp fields
.comparator
// Case-insensitive URL comparison
.comparator;
let diff = diff_with.unwrap;
Use cases:
- Ignore volatile fields (timestamps, cache values)
- Compare by ID only (treat objects equal if IDs match)
- Case-insensitive comparisons
- Normalized URL comparisons
Array element limitation:
Due to glob pattern syntax, array indices like [0], [1] require workaround patterns:
// For small, known-size arrays
.comparator // Matches [0]
.comparator // Matches [1]
// Note: ?0? = 3 chars, so won't match [10] (4 chars)
For dynamic arrays, consider comparing at the parent level or using .ignore() for specific indices.
Output Formats
let diff = diff.unwrap;
// Pretty format (human-readable)
println!;
// JSON format
let json = diff.to_json?;
// JSON Patch (RFC 6902)
let patch = diff.to_json_patch?;
// Markdown table (great for PRs)
let markdown = diff.to_markdown?;
JSON Patch Output:
Markdown Output:
| Path | Change | Old Value | New Value |
|---|---|---|---|
| name | Modified | "Alice" | "Alice Smith" |
| Modified | "alice@old.com" | "alice@new.com" |
Advanced Configuration
use ;
let config = new
// Ignore specific paths
.ignore
.ignore
// Mask sensitive data
.mask
.mask
// Float comparison tolerance
.float_tolerance
.default_float_tolerance
// Limit depth (prevent stack overflow)
.max_depth
// Limit collection size
.collection_limit
// Sequence diff algorithms (per-path or default)
.sequence_algorithm
.sequence_algorithm
.default_sequence_algorithm;
let diff = diff_with?;
Edge Cases Handled
- Floats: NaN == NaN, -0.0 == +0.0
- Large collections: Automatic elision with configurable limits
- Large byte arrays: Hex previews instead of full dumps
- Deep nesting: Configurable depth limits
- Type mismatches: Clear reporting when types differ
Performance
Diffo is designed for production use with configurable algorithms:
- IndexBased: O(n) - fastest, simple comparison
- Myers: O(ND) - optimal edit distance (D = number of differences)
- Patience: O(N log N) - human-intuitive results
- Efficient path representation with BTreeMap
Algorithm selection guide:
- Use IndexBased for simple cases or when speed is critical
- Use Myers when you need the minimal/optimal diff
- Use Patience for code, reorderings, or human review
Comparison with Alternatives
| Feature | diffo | serde-diff | json-diff |
|---|---|---|---|
| Path notation | ✅ | ❌ | ✅ |
| Secret masking | ✅ | ❌ | ❌ |
| JSON Patch (RFC 6902) | ✅ | ❌ | ✅ |
| Float tolerance | ✅ | ❌ | ❌ |
| Works with any Serialize | ✅ | ✅ | JSON only |
| Multiple formatters | ✅ | ❌ | ❌ |
| Collection limits | ✅ | ❌ | ❌ |
| Multiple diff algorithms | ✅ | ❌ | ❌ |
| Custom comparators | ✅ | ❌ | ❌ |
Roadmap
v0.2 ✅
- Multiple sequence diff algorithms (IndexBased, Myers, Patience)
- Per-path algorithm configuration
- Custom comparator functions per path
- Diff application (apply changes to produce new value)
Future releases
- Path interning for performance
- Streaming diff for very large structures
v1.0
- Stable API guarantee
- Comprehensive benchmarks vs alternatives
- Performance optimization guide
Contributing
Contributions are welcome! Please open an issue or PR on GitHub.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
Built with:
- serde - Serialization framework
- serde-value - Generic value representation