Expand description
§DeepMerge
A flexible deep merge library for Rust with policy-driven merging and derive macros featuring typed attributes.
§Features
- Policy-driven merging: Configure how different types should be merged
- Derive macro support: Automatically implement
DeepMergefor your structs - Typed attributes: Use identifiers instead of string literals for better compile-time checking
- Flexible attribute syntax: Mix string literals, identifiers, and path expressions
- Precedence rules: Field-level > struct-level > caller-provided policies
- Multiple merge strategies: Append, prepend, union, concatenation, replacement, and more
- No-std compatible: Works without the standard library (with
alloc)
§Quick Start
Add this to your Cargo.toml:
[dependencies]
deepmerge = { version = "0.1", features = ["derive"] }§Usage Examples
§Basic Usage with Typed Attributes
use deepmerge::prelude::*;
// Use clean identifier syntax instead of string literals
#[derive(DeepMerge, Debug)]
#[merge(policy(string = concat, sequence = append, bool = true_wins))]
struct Config {
pub title: String,
pub tags: Vec<String>,
pub enabled: bool,
// Field-level override
#[merge(string = keep)]
pub version: String,
}
let mut config = Config {
title: "My App".to_string(),
tags: vec!["web".to_string()],
enabled: false,
version: "1.0".to_string(),
};
let update = Config {
title: " v2".to_string(),
tags: vec!["api".to_string()],
enabled: true,
version: "2.0".to_string(),
};
config.merge_with_policy(update, &DefaultPolicy);
// Results:
// title: "My App v2" (concatenated)
// tags: ["web", "api"] (appended)
// enabled: true (true wins)
// version: "1.0" (kept original due to field-level override)§Supported Attribute Syntax
use deepmerge::prelude::*;
#[derive(DeepMerge)]
#[merge(policy(
// String literals (traditional)
string = "concat",
// Identifiers (new, cleaner syntax)
sequence = append,
bool = true_wins,
number = sum,
// Mixed syntax is supported
map = "overlay"
))]
struct FlexibleConfig { /* ... */ }§Available Merge Policies
String Policies: concat, keep, replace
Sequence Policies: append, prepend, union, extend, intersect
Boolean Policies: true_wins, false_wins, replace, keep
Number Policies: sum, max, min, replace, keep
Map Policies: overlay, union, left, right
Option Policies: take, preserve, or_left
§Policy Precedence
Policies are applied in this order of precedence:
- Field-level attributes (highest priority)
- Struct-level attributes
- Caller-provided policies (lowest priority)
§Advanced Features
§Future Path Expression Support
The infrastructure is ready for advanced path expressions:
// Coming soon:
// #[merge(policy(string = StringMerge::ConcatWithSep(", ")))]§Condition Policies with Key Extractors
// Coming soon:
// #[merge(policy(condition = changed, changed_by = "key_extractor_fn"))]§Feature Flags
derive(default): Enable the derive macrostd(default): Enable standard library supportalloc(default): Enable allocation supportindexmap: Add support forIndexMaptypes
§License
Licensed under either of:
- Apache License, Version 2.0
- MIT License
at your option.
§Contributing
Contributions are welcome! Please feel free to submit a Pull Request. A flexible deep merge library for Rust with policy-driven merging and derive macros featuring typed attributes.
This crate provides comprehensive deep merge functionality with compile-time configuration through derive macros, policy-driven behavior, and support for complex data structures.
§Features
- Policy-driven merging: Configure how different types should be merged
- Derive macro support: Automatically implement
DeepMergefor your structs - Typed attributes: Use identifiers instead of string literals for better compile-time checking
- Flexible attribute syntax: Mix string literals, identifiers, and path expressions
- Precedence rules: Field-level > struct-level > caller-provided policies
- Multiple merge strategies: Append, prepend, union, concatenation, replacement, and more
- No-std compatible: Works without the standard library (with
alloc)
§Quick Start with Prelude
For convenience, import everything you need with the prelude:
use deepmerge::prelude::*;
#[derive(DeepMerge)]
struct AppConfig {
name: String,
port: u16,
}
let mut config = AppConfig {
name: "myapp".to_string(),
port: 8080
};
let update = AppConfig {
name: "newname".to_string(),
port: 9090
};
config.merge(update);
assert_eq!(config.name, "newname");
assert_eq!(config.port, 9090);§Basic Usage with Explicit Policies
use deepmerge::prelude::*;
// Simple derive without policy attributes
#[derive(DeepMerge, Debug)]
struct Config {
pub title: String,
pub tags: Vec<String>,
pub enabled: bool,
pub version: String,
}
let mut config = Config {
title: "My App".to_string(),
tags: vec!["web".to_string()],
enabled: false,
version: "1.0".to_string(),
};
let update = Config {
title: " v2".to_string(),
tags: vec!["api".to_string()],
enabled: true,
version: "2.0".to_string(),
};
// Use explicit policy for complex merge behavior
let policy = ComposedPolicy::new(DefaultPolicy)
.with_string_merge(StringMerge::Concat)
.with_sequence_merge(SequenceMerge::Append)
.with_bool_merge(BoolMerge::TrueWins);
config.merge_with_policy(update, &policy);
// Results:
assert_eq!(config.title, "My App v2"); // concatenated
assert_eq!(config.tags, vec!["web", "api"]); // appended
assert_eq!(config.enabled, true); // true wins
assert_eq!(config.version, "2.0"); // replaced (no field-level override available yet)§Policy Configuration
Currently, policy configuration is done through explicit ComposedPolicy usage.
Derive macro policy attributes are temporarily disabled due to trait system complexity.
use deepmerge::prelude::*;
#[derive(DeepMerge)]
struct FlexibleConfig {
name: String,
items: Vec<i32>,
enabled: bool,
count: i32,
}
// Configure policies explicitly
let policy = ComposedPolicy::new(DefaultPolicy)
.with_string_merge(StringMerge::Concat)
.with_sequence_merge(SequenceMerge::Append)
.with_bool_merge(BoolMerge::TrueWins)
.with_number_merge(NumberMerge::Sum)
.with_map_merge(MapMerge::Overlay);
let mut config = FlexibleConfig { /* ... */ };
let update = FlexibleConfig { /* ... */ };
config.merge_with_policy(update, &policy);§Available Merge Policies
- String Policies:
concat,keep,replace - Sequence Policies:
append,prepend,union,extend,intersect - Boolean Policies:
true_wins,false_wins,replace,keep - Number Policies:
sum,max,min,replace,keep - Map Policies:
overlay,union,left,right - Option Policies:
take,preserve,or_left
§Using the Prelude
The prelude module provides all commonly used items in a single import:
// Import everything you need with one line
use deepmerge::prelude::*;
// Now you have access to:
// - DeepMerge trait and derive macro
// - All policy types (DefaultPolicy, ComposedPolicy)
// - All merge strategy enums (StringMerge, SequenceMerge, etc.)
// - All convenience functions (deep_merge, merged, etc.)For version stability, you can also import from a specific version:
use deepmerge::prelude::v1::*;§Policy Usage
Currently, policies are configured explicitly through ComposedPolicy.
This provides fine-grained control over merge behavior.
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct App {
name: String,
count: i32,
}
let mut a = App { name: "svc".into(), count: 2 };
let b = App { name: "new".into(), count: 3 };
// Configure specific merge behavior
let policy = ComposedPolicy::new(DefaultPolicy)
.with_string_merge(StringMerge::Replace)
.with_number_merge(NumberMerge::Sum);
a.merge_with_policy(b, &policy);
assert_eq!(a.name, "new"); // replaced
assert_eq!(a.count, 5); // summed (2 + 3)§Per-policy examples (concise)
- String policies:
- Replace (default)
- Keep
- Concat /
ConcatWithSep
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct S { s: String }
let mut a = S { s: "a".into() };
let policy = ComposedPolicy::new(DefaultPolicy).with_string_merge(StringMerge::Concat);
a.merge_with_policy(S { s: "b".into() }, &policy);
assert_eq!(a.s, "ab");- Number policies: Replace (default), Keep, Sum, Max, Min
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct N { n: i32 }
let mut a = N { n: 2 };
let policy = ComposedPolicy::new(DefaultPolicy).with_number_merge(NumberMerge::Max);
a.merge_with_policy(N { n: 5 }, &policy);
assert_eq!(a.n, 5);- Bool policies: Replace (default), Keep,
TrueWins,FalseWins
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct B { b: bool }
let mut a = B { b: false };
let policy = ComposedPolicy::new(DefaultPolicy).with_bool_merge(BoolMerge::TrueWins);
a.merge_with_policy(B { b: true }, &policy);
assert!(a.b);- Sequence policies: Append (default), Prepend, Extend, Union, Intersect
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct L { v: Vec<i32> }
let mut a = L { v: vec![1,2] };
let policy = ComposedPolicy::new(DefaultPolicy).with_sequence_merge(SequenceMerge::Append);
a.merge_with_policy(L { v: vec![2,3] }, &policy);
assert_eq!(a.v, vec![1,2,2,3]);- Map policies: Overlay (default), Union, Left, Right
use std::collections::HashMap;
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
#[merge(policy(map = overlay))]
struct M { m: HashMap<&'static str, i32> }
let mut a = M { m: [("a",1)].into_iter().collect() };
a.merge(M { m: [("a",2),("b",3)].into_iter().collect() });
assert_eq!(a.m.get("a"), Some(&2));
assert_eq!(a.m.get("b"), Some(&3));- Option policies: Take (default), Preserve,
OrLeft
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
#[merge(policy(option = preserve))]
struct O { o: Option<i32> }
let mut a = O { o: Some(1) };
a.merge(O { o: Some(2) });
assert_eq!(a.o, Some(1));Re-exports§
pub use policy::Policy;pub use policy::DefaultPolicy;pub use policy::DeepMerge;pub use policy::DeepMergeDefault;pub use policy::DeepMergeFrom;pub use policy::DeepMergeFromDefault;pub use policy::StrictReplacePolicy;pub use policy::PreservePolicy;pub use policy::DedupeCollectionsPolicy;pub use policy::ComposedPolicy;pub use policy::ScalarAction;pub use policy::SequenceMerge;pub use policy::OptionMerge;pub use policy::NumberMerge;pub use policy::MapMerge;pub use policy::BoolMerge;pub use policy::StringMerge;pub use policy::Condition;pub use policy::MergeOutcome;pub use handlers::vec_union_dedup;pub use handlers::vec_intersect_dedup;pub use handlers::vec_append_with_dedupe;pub use handlers::vec_prepend_with_dedupe;pub use handlers::vec_dedupe_in_place;pub use handlers::vec_dedupe_in_place_by_key;pub use handlers::vec_append_dedup_by_key;pub use handlers::vec_prepend_dedup_by_key;pub use handlers::vec_union_dedup_ord;pub use handlers::vec_intersect_dedup_ord;pub use handlers::option_merge_if_changed;pub use handlers::option_merge_with_key;
Modules§
- forwarding
- Forwarding implementations for smart pointers and borrowed types
- handlers
- Kind-specific merge handlers
- policy
- Policy system for configuring merge behavior
- prelude
- Prelude module for convenient imports
Functions§
- deep_
merge - Deep merge two values using the default policy
- deep_
merge_ from - Deep merge from another type using the default policy
- deep_
merge_ from_ reporting - Deep merge from another type with change reporting using the default policy
- deep_
merge_ from_ with_ policy - Deep merge from another type using a specific policy
- deep_
merge_ from_ with_ policy_ reporting - Deep merge from another type with change reporting using a specific policy
- deep_
merge_ ref - Deep merge by reference using the default policy
- deep_
merge_ ref_ with_ policy - Deep merge by reference using a specific policy
- deep_
merge_ reporting - Deep merge with change reporting using the default policy
- deep_
merge_ with_ policy - Deep merge two values using a specific policy
- deep_
merge_ with_ policy_ reporting - Deep merge with change reporting using a specific policy
- merged
- Merge two values and return a new merged value using the default policy
This provides parity with
deep_mergebut returns a new value instead of mutating - merged_
with_ policy - Merge two values and return a new merged value using a specific policy
Derive Macros§
- Deep
Merge - Derive macro for the
DeepMergetrait