Key & Value Replacements

Replace patterns in keys and/or values using literal strings or regular expressions.

Key Replacements

#![allow(unused)]
fn main() {
let result = JSONTools::new()
    .flatten()
    .key_replacement("user_profile_", "")  // Literal
    .key_replacement("r'(User|Admin)_'", "")  // Regex
    .execute(json)?;
}
result = (jt.JSONTools()
    .flatten()
    .key_replacement("user_profile_", "")
    .key_replacement("r'(User|Admin)_'", "")
    .execute(data)
)

Value Replacements

#![allow(unused)]
fn main() {
let result = JSONTools::new()
    .flatten()
    .value_replacement("@example.com", "@company.org")  // Literal
    .value_replacement("r'^super$'", "administrator")  // Regex
    .execute(json)?;
}

Key Exclusion

Unlike key_replacement (which renames matched text within a key), exclude_key drops the entire key -- and its whole value/subtree -- from the output. Matching a container key removes everything under it, without those nested keys needing to match themselves:

#![allow(unused)]
fn main() {
let json = r#"{"user": {"name": "John", "crypto_wallet": {"coin": "BTC", "balance": 100}}}"#;
let result = JSONTools::new()
    .flatten()
    .exclude_key("crypto")  // Literal
    .exclude_key("r'^secret_'")  // Regex
    .execute(json)?;
// Output: {"user.name": "John"}
}
result = (jt.JSONTools()
    .flatten()
    .exclude_key("crypto")
    .execute(data)
)

Works identically in .flatten(), .unflatten(), and .normal() mode: checked against the full dot-path in flatten/unflatten mode, and per key at each nesting level in normal mode. Additive -- call it once per keyword to exclude multiple. Array elements are never matched, since they have no key name to check.

Value Exclusion

exclude_value is exclude_key's counterpart: it drops a key-value pair based on the value's content instead of the key's name.

#![allow(unused)]
fn main() {
let json = r#"{"user": {"name": "John", "status": "banned"}}"#;
let result = JSONTools::new()
    .flatten()
    .exclude_value("banned")  // Literal
    .exclude_value("r'^flag_'")  // Regex
    .execute(json)?;
// Output: {"user.name": "John"}
}
result = (jt.JSONTools()
    .flatten()
    .exclude_value("banned")
    .execute(data)
)

Unlike exclude_key, this only ever applies to scalar leaf values (strings, numbers, booleans, null) -- containers have no single value to check, so an object or array is never itself excluded; only its individual scalar leaves can be. The check runs after any configured value_replacement/auto_convert_types have run, so a value that only matches after being replaced or converted is still caught. It's a no-op at the document root, since there's no parent key to drop the value from.

Unflatten-specific note: string values are matched against their JSON-serialized form, including the surrounding quotes -- not the unescaped logical text. A literal pattern is unaffected by this (quotes don't change substring matching), but a regex with anchors needs to account for them: use r'^"admin"$', not r'^admin$', to match a value that's exactly "admin" in .unflatten() mode.

Regex Syntax

Wrap a pattern in r'...' (e.g. r'^prefix_') to use it as a regular expression. Any pattern not wrapped this way is matched as a literal, exact substring -- including patterns that contain characters that would otherwise be regex metacharacters (., $, (, etc.). The regex engine uses standard Rust regex syntax.

PatternDescription
"old"Literal string replacement
"r'^prefix_'"Regex: match start of string
"r'(a|b)_'"Regex: alternation
"r'\d+'"Regex: digit sequences

A malformed r'...' pattern (invalid regex syntax) is silently treated as "no match" for that pattern rather than raising an error -- test your patterns to confirm they compile as intended.

Multiple Replacements

You can chain multiple key and value replacements. They are applied in order:

#![allow(unused)]
fn main() {
let result = JSONTools::new()
    .flatten()
    .key_replacement("prefix_", "")
    .key_replacement("_suffix", "")
    .key_replacement("_", ".")
    .value_replacement("@old.com", "@new.com")
    .value_replacement("r'^admin$'", "administrator")
    .execute(json)?;
}

Examples

Easy: a single value replacement

import json_tools_rs as jt

data = {"user": {"email": "john@old-domain.com"}}
result = (jt.JSONTools()
    .flatten()
    .value_replacement("@old-domain.com", "@new-domain.com")
    .execute(data)
)
# {'user.email': 'john@new-domain.com'}

Medium: key + value replacement with regex

data = {"User_Name": "Alice", "User_Status": "super"}
result = (jt.JSONTools()
    .flatten()
    .key_replacement("r'^User_'", "")
    .value_replacement("r'^super$'", "administrator")
    .execute(data)
)
# {'Name': 'Alice', 'Status': 'administrator'}

Hard: normalizing an API response

Combines key replacement, value replacement, key exclusion, and value exclusion in one pipeline -- dropping an internal-only key, a sensitive subtree, and a banned-status record, while cleaning up the surviving keys and values:

data = {
    "api_response": {
        "user_id": "1001",
        "user_email": "john@old-domain.com",
        "user_status": "banned",
        "internal_debug_token": "xyz123",
        "crypto_wallet": {"coin": "BTC", "balance": 100},
    }
}

result = (jt.JSONTools()
    .flatten()
    .separator("::")
    .lowercase_keys(True)
    .key_replacement("r'^api_response::'", "")
    .key_replacement("_", ".")
    .value_replacement("@old-domain.com", "@new-domain.com")
    .exclude_key("internal")
    .exclude_key("crypto")
    .exclude_value("banned")
    .execute(data)
)
# {'user.id': '1001', 'user.email': 'john@new-domain.com'}

user_status is dropped because its value is "banned"; internal_debug_token and crypto_wallet.* are dropped by key. Note that exclude_key/exclude_value patterns are checked against keys after key_replacement has already run -- exclude_key uses "internal" (not "internal_") here because by the time the check runs, key_replacement("_", ".") has already turned internal_debug_token into internal.debug.token, so a pattern anchored on the underscore would no longer match.