Flattening & Unflattening
Flattening
Flattening converts nested JSON into a flat key-value structure using dot-separated (or custom) keys.
// Input
{"user": {"name": "John", "address": {"city": "NYC", "zip": "10001"}}}
// Output (flattened)
{"user.name": "John", "user.address.city": "NYC", "user.address.zip": "10001"}
Arrays
Arrays are flattened with numeric indices:
// Input
{"users": [{"name": "Alice"}, {"name": "Bob"}]}
// Output
{"users.0.name": "Alice", "users.1.name": "Bob"}
Custom Separators
Use .separator() to change the key delimiter:
#![allow(unused)] fn main() { let result = JSONTools::new() .flatten() .separator("::") .execute(json)?; // {"user::name": "John", "user::address::city": "NYC"} }
result = jt.JSONTools().flatten().separator("::").execute(data)
Unflattening
Unflattening reverses the process, reconstructing nested structures from flat keys.
// Input
{"user.name": "John", "user.address.city": "NYC"}
// Output (unflattened)
{"user": {"name": "John", "address": {"city": "NYC"}}}
Numeric keys reconstruct arrays:
// Input
{"users.0.name": "Alice", "users.1.name": "Bob"}
// Output
{"users": [{"name": "Alice"}, {"name": "Bob"}]}
Roundtrip
Flattening and unflattening are perfect inverses. You can flatten data, apply transformations, then unflatten to recover the original structure:
#![allow(unused)] fn main() { let original = r#"{"user": {"name": "John", "scores": [10, 20, 30]}}"#; // Flatten let flat = JSONTools::new().flatten().execute(original)?; // Unflatten back let restored = JSONTools::new().unflatten().execute( &flat.try_into_single()? )?; // Matches original structure }
All configuration options (filtering, replacements, collision handling, type conversion) work with both .flatten() and .unflatten() modes.
Examples
Easy: flatten a nested object
#![allow(unused)] fn main() { let result = JSONTools::new() .flatten() .execute(r#"{"user": {"name": "Alice", "age": 30}}"#)?; // {"user.name": "Alice", "user.age": 30} }
result = jt.JSONTools().flatten().execute({"user": {"name": "Alice", "age": 30}})
# {'user.name': 'Alice', 'user.age': 30}
Medium: arrays of objects, custom separator, round-trip
import json_tools_rs as jt
data = {"users": [{"name": "Alice", "roles": ["admin", "editor"]}, {"name": "Bob", "roles": []}]}
flat = jt.JSONTools().flatten().separator("::").execute(data)
# {'users::0::name': 'Alice', 'users::0::roles::0': 'admin', 'users::0::roles::1': 'editor',
# 'users::1::name': 'Bob', 'users::1::roles': []}
# Note: Bob's empty "roles" array is kept as a literal [] value under its own key --
# only a *non-empty* container gets recursively flattened into per-element keys.
restored = jt.JSONTools().unflatten().separator("::").execute(flat)
# {'users': [{'name': 'Alice', 'roles': ['admin', 'editor']}, {'name': 'Bob', 'roles': []}]}
# Exact round trip, including Bob's empty array.
Hard: flatten -> transform -> unflatten pipeline
Filtering, key/value transforms, and type conversion all run during .flatten();
.unflatten() only reconstructs structure from whatever flat keys survive, so a single
flatten call can prepare data that a later unflatten call reconstructs with fewer keys
and already-converted types:
import json_tools_rs as jt
data = {
"Order_ID": "ORD-1001",
"Customer": {"Name": "Jane Doe", "Email": "jane@old-domain.com"},
"Items": [
{"sku": "A1", "qty": "2", "price": "19.99"},
{"sku": "B2", "qty": "1", "price": "9.5"},
],
"Notes": None,
}
flat = (
jt.JSONTools()
.flatten()
.separator("::")
.lowercase_keys(True)
.auto_convert_types(True)
.remove_nulls(True)
.execute(data)
)
# {'order_id': 'ORD-1001', 'customer::name': 'Jane Doe',
# 'customer::email': 'jane@old-domain.com', 'items::0::sku': 'A1',
# 'items::0::qty': 2, 'items::0::price': 19.99, 'items::1::sku': 'B2',
# 'items::1::qty': 1, 'items::1::price': 9.5}
# "Notes" is gone entirely -- remove_nulls ran before the flat map was built, so
# there's no "notes" key left for unflatten to see.
restored = jt.JSONTools().unflatten().separator("::").execute(flat)
# {'order_id': 'ORD-1001', 'customer': {'name': 'Jane Doe', 'email': 'jane@old-domain.com'},
# 'items': [{'sku': 'A1', 'qty': 2, 'price': 19.99}, {'sku': 'B2', 'qty': 1, 'price': 9.5}]}
The nested shape is fully restored, but qty/price come back as numbers (not the
original strings) and notes never reappears -- unflatten can only rebuild structure
from the keys it's given, it has no memory of what flatten discarded or converted.