RJD - Rust JSON Diff
Compare JSON files or strings and output the differences.
Usage
Options
--sort, -s- Sort keys alphabetically in the output (useful for consistent diffs)--stdin- Read the second JSON input from stdin instead of a file argument--ignore-json <IGNORE_JSON>- JSON file containing paths to ignore (can be specified multiple times)--inline- Force inputs to be treated as inline JSON (disables file path detection)--max-file-size <SIZE>- Maximum file size in bytes (default: 104857600 = 100MB). Prevents loading extremely large files.--max-depth <DEPTH>- Maximum JSON nesting depth (default: 1000). Prevents stack overflow from deeply nested JSON.--follow-symlinks- Follow symbolic links instead of rejecting them (default: reject symlinks for security)
Environment Variables
RJD_MAX_FILE_SIZE- Default maximum file size in bytes (overridden by--max-file-size)RJD_MAX_JSON_DEPTH- Default maximum JSON nesting depth (overridden by--max-depth)RJD_FOLLOW_SYMLINKS- Set to1ortrueto follow symlinks by default (overridden by--follow-symlinks)
Resource Limits
RJD includes built-in resource limits to prevent denial-of-service attacks and accidental resource exhaustion:
- File Size Limit: Prevents loading extremely large files that could exhaust memory (default: 100MB)
- JSON Depth Limit: Prevents processing deeply nested JSON structures that could cause stack overflow (default: 1000 levels)
Both limits can be customized via environment variables or CLI flags. CLI flags take precedence over environment variables.
Security Considerations
- Symlinks: By default, RJD rejects symbolic links to prevent unauthorized file access. Use
--follow-symlinksonly with trusted input. - Resource Limits: Adjust limits based on your use case. Production environments should use conservative limits.
- Input Validation: All file paths are validated before processing. Nonexistent files or invalid ignore patterns will cause immediate errors.
Examples
File Comparison
file1.json:
file2.json:
Output (changes format):
Output (rfc6902 format):
Output (after format):
The after format preserves the key order from file2.json, showing only properties that were added or modified.
Inline JSON
# Simple
# → {
# "added": [],
# "removed": [],
# "modified": [
# {
# "path": "a",
# "old_value": 1,
# "new_value": 2
# }
# ]
# }
# Nested
# → {
# "added": [
# {
# "path": "user.email",
# "value": "x@y.com"
# }
# ],
# "removed": [],
# "modified": [
# {
# "path": "user.age",
# "old_value": 30,
# "new_value": 31
# }
# ]
# }
# Arrays
# → {
# "added": [],
# "removed": [],
# "modified": [
# {
# "path": "items[0].id",
# "old_value": 1,
# "new_value": 2
# }
# ]
# }
Stdin
# Read second input from stdin
|
# Pipe from a file
|
# Use with process substitution
Resource Limits
# Set custom file size limit (50MB)
# Set custom JSON depth limit
# Use environment variables
Inline JSON Mode
# Force inputs to be treated as inline JSON
# Useful when file names conflict with JSON syntax
Symlink Handling
# Default behavior: reject symlinks (secure)
# Error: Symlink rejected: data.json
# Follow symlinks (use with trusted input)
# Set via environment variable
Path Notation
user.name→ nested object propertyitems[0]→ array elementusers[0].email→ nested array/object property
Library Usage
RJD can be used as a library in your Rust projects. Add to your Cargo.toml:
[]
= "1.2"
Basic Diffing
use ;
use json;
let old = json!;
let new = json!;
let changes = diff;
// Iterate over changes
for change in changes.iter_added
Error Handling
Formatters now return Result for safe error handling:
use create_formatter;
// Invalid format returns Result::Err
match create_formatter
Performance for Large Diffs
For diffs with thousands of changes, use the iterator-based filtering to avoid allocations:
use Changes;
// Old approach (clones changes)
let filtered = changes.filter_ignore_patterns;
// New approach (zero-copy iterator)
let filtered: = changes
.iter_filtered_changes
.collect;
// Or use directly with lazy evaluation
for change in changes.iter_filtered_changes
Performance impact: For diffs with 10,000 changes, the iterator approach reduces allocations by 50-90% and scales linearly with change count.
Root Path Handling
When diffing primitive values or replacing the entire JSON value, changes will have an empty path (""):
let old = json!;
let new = json!;
let changes = diff;
// Root-level change has empty path
assert_eq!;
Performance Notes
- Large Files: RJD handles files with 10,000+ changes efficiently
- Memory Usage: Iterator-based filtering (see above) minimizes memory for large diffs
- Resource Limits: Use
--max-file-sizeand--max-depthto prevent excessive memory usage - Filtering: The
--ignore-jsonflag uses optimized pattern matching with O(1) lookup