How does serde_json::Map::retain filter key-value pairs in place compared to iteration-based filtering?

serde_json::Map::retain removes key-value pairs that don't satisfy a predicate in a single in-place operation, avoiding the allocation and copying overhead of iteration-based filtering which requires collecting matching elements into a new map. The retain method modifies the map directly, preserving the original allocation and only shifting remaining elements, while iteration-based approaches create a new map and move or clone the retained elements.

The retain Method Signature

use serde_json::Map;
use serde_json::Value;
 
fn method_signature() {
    let mut map: Map<String, Value> = Map::new();
    
    // retain removes entries where predicate returns false
    map.retain(|key, value| {
        // Return true to keep, false to remove
        value.is_string()
    });
    
    // The signature is:
    // pub fn retain<F>(&mut self, f: F)
    // where
    //     F: FnMut(&String, &mut Value) -> bool
}

retain takes a closure that receives a reference to the key and a mutable reference to the value.

Basic retain Usage

use serde_json::{Map, Value, json};
 
fn basic_retain() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("name".to_string(), json!("Alice"));
    map.insert("age".to_string(), json!(30));
    map.insert("active".to_string(), json!(true));
    map.insert("score".to_string(), json!(null));
    
    // Keep only string values
    map.retain(|_key, value| value.is_string());
    
    assert_eq!(map.len(), 1);
    assert!(map.contains_key("name"));
    assert!(!map.contains_key("age"));
    
    // Map now only contains: {"name": "Alice"}
}

retain modifies the map in place, keeping only entries matching the predicate.

Iteration-Based Filtering Approach

use serde_json::{Map, Value, json};
 
fn iteration_filtering() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("name".to_string(), json!("Alice"));
    map.insert("age".to_string(), json!(30));
    map.insert("active".to_string(), json!(true));
    map.insert("score".to_string(), json!(null));
    
    // Iteration-based: create new map with matching entries
    let mut filtered: Map<String, Value> = Map::new();
    for (key, value) in map.iter() {
        if value.is_string() {
            filtered.insert(key.clone(), value.clone());
        }
    }
    
    // Or using collect:
    let filtered2: Map<String, Value> = map.iter()
        .filter(|(_, value)| value.is_string())
        .map(|(key, value)| (key.clone(), value.clone()))
        .collect();
    
    // Original map unchanged
    assert_eq!(map.len(), 4);
    // Filtered has 1 entry
    assert_eq!(filtered.len(), 1);
}

Iteration-based filtering creates a new map, cloning retained elements.

Memory and Allocation Differences

use serde_json::{Map, Value, json};
 
fn memory_comparison() {
    // Create a large map
    let mut map: Map<String, Value> = (0..1000)
        .map(|i| (format!("key{}", i), json!(i)))
        .collect();
    
    // retain approach:
    // - Operates in place on existing allocation
    // - No new allocation for the map itself
    // - Elements that stay don't need to be moved/cloned
    // - Elements that are removed are dropped in place
    map.retain(|key, _| key.starts_with("key1"));
    // Memory: original allocation, but some entries removed
    
    // Iteration approach:
    // - Creates new Map allocation
    // - Clones each retained key-value pair
    // - Old map stays in memory until dropped
    // - More memory pressure during operation
    
    // For large maps with few removals:
    // retain is much more efficient - no cloning of kept entries
    
    // For large maps with many removals:
    // retain still wins - operates on single allocation
    
    // Trade-off: iteration can release old memory by dropping original
}

retain avoids allocation and cloning; iteration creates copies.

Access to Mutable Values in retain

use serde_json::{Map, Value, json};
 
fn mutable_access() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("name".to_string(), json!("  alice  "));
    map.insert("age".to_string(), json!("30"));
    map.insert("notes".to_string(), json!("  some notes  "));
    
    // retain gives mutable access to values
    map.retain(|key, value| {
        // Can mutate values while filtering
        if value.is_string() {
            if let Some(s) = value.as_str() {
                *value = json!(s.trim());
            }
        }
        // Return true to keep all entries
        true
    });
    
    assert_eq!(map.get("name").unwrap(), "alice");
    assert_eq!(map.get("notes").unwrap(), "some notes");
}

retain provides mutable access to values, enabling transformation during filtering.

Combining Filtering and Mutation

use serde_json::{Map, Value, json};
 
fn filter_and_mutate() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("name".to_string(), json!("alice"));
    map.insert("age".to_string(), json!(30));
    map.insert("email".to_string(), json!("alice@example.com"));
    map.insert("temp".to_string(), json!(true));
    
    // Remove certain keys AND normalize remaining string values
    map.retain(|key, value| {
        // Remove temporary fields
        if key == "temp" {
            return false;
        }
        
        // Normalize string values (lowercase)
        if let Some(s) = value.as_str() {
            *value = json!(s.to_lowercase());
        }
        
        true
    });
    
    assert_eq!(map.get("name").unwrap(), "alice");
    assert_eq!(map.get("email").unwrap(), "alice@example.com");
    assert!(!map.contains_key("temp"));
}

Combine filtering logic with value transformation in one pass.

Performance Characteristics

use serde_json::{Map, Value, json};
use std::time::Instant;
 
fn performance_demo() {
    // Create a map with 10,000 entries
    let mut map: Map<String, Value> = (0..10000)
        .map(|i| {
            let key = format!("key{}", i);
            let value = if i % 3 == 0 {
                json!(null)
            } else {
                json!(i)
            };
            (key, value)
        })
        .collect();
    
    // retain: O(n) time, O(1) extra space
    let start = Instant::now();
    map.retain(|_, value| !value.is_null());
    let retain_time = start.elapsed();
    
    // Reset map
    let mut map: Map<String, Value> = (0..10000)
        .map(|i| (format!("key{}", i), if i % 3 == 0 { json!(null) } else { json!(i) }))
        .collect();
    
    // iteration + collect: O(n) time, O(n) space for new map
    let start = Instant::now();
    let filtered: Map<String, Value> = map.iter()
        .filter(|(_, v)| !v.is_null())
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();
    let collect_time = start.elapsed();
    
    // retain is typically faster because:
    // 1. No cloning of retained values
    // 2. No allocation of new map structure
    // 3. Single pass through the data
    
    println!("retain: {:?}", retain_time);
    println!("collect: {:?}", collect_time);
}

retain is faster due to no cloning or new allocation.

Iteration with Removal Pitfalls

use serde_json::{Map, Value, json};
 
fn removal_pitfalls() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("a".to_string(), json!(1));
    map.insert("b".to_string(), json!(2));
    map.insert("c".to_string(), json!(3));
    
    // WRONG: Cannot remove during iteration
    // for (key, _) in map.iter() {
    //     if key == "b" {
    //         map.remove(key); // Compile error!
    //     }
    // }
    
    // WRONG: Using iter_mut to remove
    // for (key, _) in map.iter_mut() {
    //     // Cannot remove from map during mutable iteration
    // }
    
    // CORRECT: Use retain for safe in-place filtering
    map.retain(|key, _| key != "b");
    
    assert_eq!(map.len(), 2);
    assert!(!map.contains_key("b"));
    
    // Alternative: drain + collect (still creates new collection)
    // Or: collect keys to remove, then remove one by one
}

retain is the safe way to remove during iteration; direct removal would cause iterator invalidation.

Key-Based Filtering

use serde_json::{Map, Value, json};
 
fn key_filtering() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("user_id".to_string(), json!(123));
    map.insert("user_name".to_string(), json!("alice"));
    map.insert("admin_id".to_string(), json!(456));
    map.insert("admin_name".to_string(), json!("bob"));
    map.insert("temp_data".to_string(), json!([]));
    
    // Keep only user_* keys
    map.retain(|key, _| key.starts_with("user_"));
    
    assert!(map.contains_key("user_id"));
    assert!(map.contains_key("user_name"));
    assert!(!map.contains_key("admin_id"));
    
    // Or using a set of allowed keys
    let allowed = std::collections::HashSet::from([
        "user_id".to_string(),
        "user_name".to_string(),
    ]);
    map.retain(|key, _| allowed.contains(key));
}

Filter by key patterns or whitelists using the key parameter.

Value-Based Filtering

use serde_json::{Map, Value, json};
 
fn value_filtering() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("count".to_string(), json!(42));
    map.insert("name".to_string(), json!("test"));
    map.insert("active".to_string(), json!(true));
    map.insert("data".to_string(), json!(null));
    map.insert("items".to_string(), json!([1, 2, 3]));
    
    // Keep only non-null values
    map.retain(|_, value| !value.is_null());
    assert!(!map.contains_key("data"));
    
    // Keep only numbers
    let mut map2: Map<String, Value> = map.clone();
    map2.retain(|_, value| value.is_number());
    assert!(map2.contains_key("count"));
    assert!(!map2.contains_key("name"));
    
    // Keep only arrays
    let mut map3: Map<String, Value> = map.clone();
    map3.retain(|_, value| value.is_array());
    assert!(map3.contains_key("items"));
    
    // Keep only truthy values
    let mut map4: Map<String, Value> = map.clone();
    map4.retain(|_, value| {
        match value {
            Value::Null => false,
            Value::Bool(b) => *b,
            Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
            Value::String(s) => !s.is_empty(),
            Value::Array(a) => !a.is_empty(),
            Value::Object(o) => !o.is_empty(),
        }
    });
}

Filter by value type, content, or computed properties.

Complex Filtering Logic

use serde_json::{Map, Value, json};
 
fn complex_filtering() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("users".to_string(), json!([{"id": 1}, {"id": 2}]));
    map.insert("config".to_string(), json!({"debug": true}));
    map.insert("count".to_string(), json!(100));
    map.insert("empty_arr".to_string(), json!([]));
    map.insert("metadata".to_string(), json!({"version": "1.0"}));
    
    // Complex: keep non-empty objects and arrays, all other truthy values
    map.retain(|key, value| {
        match value {
            Value::Array(arr) => {
                // Keep non-empty arrays, unless key suggests temporary
                !arr.is_empty() && !key.starts_with("temp")
            }
            Value::Object(obj) => {
                // Keep non-empty objects
                !obj.is_empty()
            }
            Value::Null => false,
            Value::Bool(b) => *b,
            Value::Number(_) => true,
            Value::String(s) => !s.is_empty(),
        }
    });
    
    assert!(map.contains_key("users"));    // non-empty array
    assert!(map.contains_key("config"));   // non-empty object
    assert!(map.contains_key("count"));    // number
    assert!(!map.contains_key("empty_arr")); // empty array removed
}

Combine multiple conditions for sophisticated filtering.

Comparison: retain vs Iteration Patterns

use serde_json::{Map, Value, json};
 
fn pattern_comparison() {
    let mut original: Map<String, Value> = Map::new();
    original.insert("a".to_string(), json!(1));
    original.insert("b".to_string(), json!(2));
    original.insert("c".to_string(), json!(3));
    
    // Pattern 1: retain (in-place)
    let mut map1 = original.clone();
    map1.retain(|key, _| key != "b");
    // Pros:
    // - No allocation
    // - No cloning
    // - Single pass
    // - Original map modified
    // Cons:
    // - Original map modified (might need clone first)
    
    // Pattern 2: clone + retain
    let mut map2 = original.clone();
    map2.retain(|key, _| key != "b");
    // Pros: same as retain
    // Cons: requires clone if you need to preserve original
    
    // Pattern 3: iteration + collect
    let map3: Map<String, Value> = original.iter()
        .filter(|(key, _)| key != &"b")
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();
    // Pros:
    // - Original preserved
    // - Can transform during collection
    // Cons:
    // - Allocates new map
    // - Clones retained values
    
    // Pattern 4: drain + filter + collect
    let mut map4 = original.clone();
    let drained: Map<String, Value> = map4.drain()
        .filter(|(key, _)| key != "b")
        .collect();
    // Pros:
    // - Takes ownership, no cloning needed
    // - Original emptied (memory released)
    // Cons:
    // - Original destroyed
    // - Creates new map allocation
}

Choose based on whether you need the original and want to avoid allocation.

When to Use retain

use serde_json::{Map, Value, json};
 
fn when_to_use_retain() {
    // BEST for retain:
    
    // 1. Large maps where allocation cost matters
    let mut large_map: Map<String, Value> = (0..100000)
        .map(|i| (format!("key{}", i), json!(i)))
        .collect();
    large_map.retain(|key, _| key.ends_with('7'));
    // No allocation, just removal
    
    // 2. When you don't need the original
    fn process_request_data(data: &mut Map<String, Value>) {
        // Remove internal fields before returning
        data.retain(|key, _| !key.starts_with('_'));
    }
    
    // 3. Single-pass filtering + mutation
    let mut map: Map<String, Value> = Map::new();
    map.insert("name".to_string(), json!("  alice  "));
    map.insert("temp".to_string(), json!(null));
    
    map.retain(|key, value| {
        if key == "temp" {
            return false;
        }
        if let Some(s) = value.as_str() {
            *value = json!(s.trim());
        }
        true
    });
    // One pass for both filtering and transformation
    
    // 4. Chained with other in-place operations
    let mut config: Map<String, Value> = Map::new();
    // ... populate config ...
    config.retain(|_, v| !v.is_null());  // Remove nulls
    config.retain(|k, _| k.starts_with("valid_"));  // Then filter keys
}

Use retain for large maps, in-place modifications, and combined filtering/mutation.

When to Prefer Iteration-Based Filtering

use serde_json::{Map, Value, json};
 
fn when_to_use_iteration() {
    // BEST for iteration-based:
    
    // 1. When you need both original and filtered
    let original: Map<String, Value> = Map::new();
    // ... populate ...
    let filtered: Map<String, Value> = original.iter()
        .filter(|(_, v)| v.is_string())
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();
    // original still available
    
    // 2. When transforming during filter
    let transformed: Map<String, Value> = original.iter()
        .filter(|(k, _)| k.starts_with("user_"))
        .map(|(k, v)| {
            let transformed_value = match v {
                Value::String(s) => json!(s.to_uppercase()),
                other => other.clone(),
            };
            (k.clone(), transformed_value)
        })
        .collect();
    
    // 3. When collecting into a different type
    let as_vec: Vec<(String, Value)> = original.iter()
        .filter(|(_, v)| v.is_number())
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();
    
    // 4. Small maps where allocation cost is negligible
    let mut small_map: Map<String, Value> = Map::new();
    small_map.insert("a".to_string(), json!(1));
    small_map.insert("b".to_string(), json!(2));
    // Cloning small map is trivial
    
    let filtered = small_map.iter()
        .filter(|(_, v)| v.is_number())
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect::<Map<String, Value>>();
}

Use iteration when you need the original, want transformations, or map is small.

Drain as Alternative

use serde_json::{Map, Value, json};
 
fn drain_alternative() {
    let mut map: Map<String, Value> = Map::new();
    map.insert("a".to_string(), json!(1));
    map.insert("b".to_string(), json!(2));
    map.insert("c".to_string(), json!(3));
    
    // drain takes ownership, no cloning needed
    let retained: Map<String, Value> = map.drain()
        .filter(|(key, _)| key != "b")
        .collect();
    
    // Original map is now empty
    assert!(map.is_empty());
    
    // Retained has filtered entries (no cloning occurred)
    assert_eq!(retained.len(), 2);
    assert!(!retained.contains_key("b"));
    
    // Compare to retain:
    // - retain: modifies in place, keeps original allocation
    // - drain + collect: empties original, creates new allocation
    // - Both avoid cloning, but drain releases original memory
}

drain takes ownership, avoiding clones while creating a new collection.

Summary Table

fn summary() {
    // | Method          | Allocation | Cloning | Original  | Use Case           |
    // |-----------------|------------|---------|-----------|-------------------|
    // | retain          | None       | None    | Modified  | Large maps, inplace|
    // | iter+collect    | New map    | Yes     | Preserved | Need original     |
    // | drain+collect   | New map    | No      | Emptied   | Release memory    |
    // | clone+retain    | Clone      | Yes     | Preserved | Need original     |
    
    // | Operation       | Time    | Extra Space | Mutates Original |
    // |------------------|---------|-------------|------------------|
    // | retain           | O(n)    | O(1)        | Yes              |
    // | iter+collect     | O(n)    | O(n)        | No               |
    // | drain+collect    | O(n)    | O(n)        | Yes (empties)    |
    
    // | Scenario                    | Best Method        |
    // |-----------------------------|--------------------| 
    // | Large map, don't need orig  | retain             |
    // | Need original unchanged     | iter+collect       |
    // | Want to release orig memory | drain+collect      |
    // | Filter + transform          | iter+map+collect   |
    // | Filter + mutate             | retain             |
}

Synthesis

Quick reference:

use serde_json::{Map, Value, json};
 
let mut map: Map<String, Value> = /* ... */;
 
// retain: in-place, no allocation, no cloning
map.retain(|key, value| {
    // Return true to keep, false to remove
    // Can mutate value through mutable reference
    !value.is_null() && key.starts_with("valid_")
});
 
// Alternative: iteration-based (creates new map, clones values)
let filtered: Map<String, Value> = original.iter()
    .filter(|(k, v)| !v.is_null() && k.starts_with("valid_"))
    .map(|(k, v)| (k.clone(), v.clone()))
    .collect();

Key insight: Map::retain is the idiomatic and efficient way to filter a JSON map in place. It operates in a single pass, modifying the map directly without allocating a new map or cloning retained values—O(n) time and O(1) extra space. The predicate receives (&String, &mut Value), allowing both key inspection and value mutation during filtering. Compare this to iteration-based approaches: collecting into a new map requires O(n) allocation and cloning each retained value. The drain method offers a middle ground—it takes ownership of entries so no cloning occurs, but still requires allocating a new map for the result. Use retain when you want to modify the original map and care about allocation overhead (large maps, hot paths). Use iteration-based collection when you need to preserve the original map or want to transform values during filtering. The mutable value reference in retain's predicate enables powerful patterns like normalizing string values while simultaneously removing unwanted entries, all in a single efficient pass. Unlike attempting to remove during iteration (which would invalidate the iterator), retain handles the complexity of in-place modification safely.