KM-TRAVERSAL
Introduction
traverseIn is a powerful TypeScript utility for traversing and modifying complex nested data structures (objects and arrays) using expressive pattern syntax. It provides fine-grained control over data traversal with support for deep nesting, conditional filtering, and in-place modifications.
Key Features
- 🚀 Expressive pattern syntax for precise data navigation
- 🔍 Advanced filtering with key/value conditions
- ✏️ In-place modification of keys and values
- 🌳 Deep recursion with depth control
- ⚡ Multi-key selection for simultaneous access
- 🔄 Dual syntax support for explicit and shortcut patterns
- 🧩 Extensible condition system for custom logic
- 📋 Path tracking with full object paths
- 🧠 Smart quoting for flexible pattern definitions
Installation
npm i km-traversal
Core Concepts
Pattern Syntax
Pattern Type |
Syntax Example |
Description |
Property Access |
user.profile |
Direct property access |
Single Star |
(*) or * (shortcut) |
Iterate all immediate children |
Double Star |
(**) or ** (shortcut) |
Recursive descent through all levels |
Limited Recursion |
(*3*) |
Recursive descent limited to 3 levels |
Single Key |
("id") or (id) |
Access specific property |
Multi Key |
("id","name") or (id,name) |
Access multiple specific properties |
Object Condition |
({"key":"title","value.includes":"urgent"}) |
AND conditions on key/value |
Array Condition |
([{"value.>":5},{"value.<":10}]) |
OR of AND conditions |
Condition Syntax
Conditions always specify what they target:
- key.condition: Applies to the property key
- value.condition: Applies to the property value
- condition: Alias for value.condition (default behavior)
Pattern |
Meaning |
{"value.startsWith":"a"} |
Value starts with "a" |
{"key.equalWith":"email"} |
Key is exactly "email" |
{"key.includes":"name"} |
Key contains "name" |
{"isNumber":true} |
Value is a number |
{"!isArray":true} |
Value is NOT an array |
Default Conditions
Key/Value Operations
Condition |
Description |
Example Usage |
equalWith |
Strict equality |
{"key.equalWith":"email"} |
notEqual |
Strict inequality |
{"value.notEqual":null} |
includes |
String contains value |
{"value.includes":"urgent"} |
String Operations
Condition |
Description |
Example Usage |
startsWith |
Starts with value |
{"value.startsWith":"https"} |
endsWith |
Ends with value |
{"value.endsWith":"@gmail.com"} |
matches |
Matches regex pattern |
{"value.matches":"\d{3}-\d{4}"} |
Numeric Operations
Condition |
Description |
Example Usage |
greaterThan |
> value |
{"value.greaterThan":18} |
lessThan |
< value |
{"value.lessThan":100} |
between |
Between [min, max] |
{"value.between":[5,10]} |
Type Checking
Condition |
Description |
Example Usage |
isString |
String type |
{"value.isString":true} |
isNumber |
Number type |
{"value.isNumber":true} |
isArray |
Array type |
{"value.isArray":true} |
isObject |
Plain object |
{"value.isObject":true} |
Array Operations
Condition |
Description |
Example Usage |
arrayIncludes |
Array contains value |
{"value.arrayIncludes":"admin"} |
length |
Array has exact length |
{"value.length":5} |
API Reference
Core Functions
traverseIn<ENTRY_DATA, OPTIONS>(
data: ENTRY_DATA,
options: ICustomEachOptions,
patterns: (
string |
string[] |
(({setCondName}) => string)
)[],
callbacks: Callback[] // Functions to execute at target nodes
): void
Adapter Pattern
const { register } = adapter();
const traverser = register(customConditions);
traverser.traverseIn(data, patterns, callbacks);
Callback Parameters
callback({
key: string | number,
value: any,
objectPath: (string | number)[],
parent: any,
setKey: (newKey: string) => void,
setValue: (newValue: any) => void,
remove: () => void,
removeNears: () => void
})
Configuration Options
Option |
Type |
Default |
Description |
injectedConditions |
Array |
Required |
Custom conditions to extend functionality |
shortcuts.singleStar |
boolean |
true |
Enable * syntax for single-star |
shortcuts.doubleStar |
boolean |
true |
Enable ** syntax for double-star |
Usage Examples
Basic Traversal
import { traverseIn, defaultConditions } from './traverseIn';
const data = {
users: [
{ id: 1, name: 'John', contact: { email: 'john@example.com' } },
{ id: 2, name: 'Jane', contact: { email: 'jane@example.com' } },
],
};
traverseIn(
data,
{ injectedConditions: defaultConditions },
['users.(*).contact.email'],
[
({ value, setValue }) => {
setValue(value.toUpperCase());
},
]
);
Advanced Filtering
const productData = {
inventory: [
{ id: 1, title: 'Laptop', price: 1200, tags: ['electronics', 'premium'] },
{ id: 2, title: 'Desk Lamp', price: 35, tags: ['furniture'] },
{ id: 3, title: 'Monitor', price: 300, tags: ['electronics'] },
],
};
traverseIn(
productData,
{ injectedConditions: defaultConditions },
[
'inventory.([{ "key:equalWith":"price", "value.lessThan":1000},{ "key:equalWith":"tags", "value.arrayIncludes":"electronics"}])',
],
[
({ value }) => {
console.log('Filtered item:', value.title);
},
]
);
Key-Based Operations
const userData = {
user_list: [
{ user_id: 1, user_name: 'John' },
{ user_id: 2, user_name: 'Jane' },
],
};
traverseIn(
userData,
{ injectedConditions: defaultConditions },
['user_list.(*).user_id'],
[
({ setKey }) => {
setKey('id');
},
]
);
traverseIn(
userData,
{ injectedConditions: defaultConditions },
['user_list.(*).({"key.includes":"name"})'],
[
({ value }) => {
console.log('Name property:', value);
},
]
);
Recursive Search
const nestedData = {
a: {
b: {
c: 'Target 1',
d: { e: 'Target 2' },
},
f: { g: 'Target 3' },
},
};
traverseIn(
nestedData,
{ injectedConditions: defaultConditions },
[['(**)']],
[
({ value, objectPath }) => {
if (typeof value === 'string' && value.startsWith('Target')) {
console.log(`Found ${value} at: ${objectPath.join('.')}`);
}
},
]
);
Custom Conditions
const customConditions = [
...defaultConditions,
{
name: 'validEmail',
action: (_, __, target) =>
typeof target === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(target),
},
{
name: 'isEven',
action: (_, __, target) => typeof target === 'number' && target % 2 === 0,
},
];
const data = {
contacts: [
{ id: 1, email: 'john@example.com' },
{ id: 2, email: 'invalid' },
{ id: 3, email: 'jane@example.com' },
],
};
traverseIn(
data,
{ injectedConditions: customConditions },
['contacts.(*).email.({"value.validEmail":true})', 'contacts.(*).id.({"value.isEven":true})'],
[({ value }) => console.log('Valid email:', value), ({ value }) => console.log('Even ID:', value)]
);
Using the Adapter
import { adapter } from './traverseIn';
const productTraverser = adapter().register([
...defaultConditions,
{
name: 'inStock',
action: (_, value) => value.inventory > 0,
},
{
name: 'onSale',
action: (_, value) => value.discount > 0,
},
]);
const products = {
items: [
{ id: 1, name: 'Laptop', price: 1200, inventory: 5, discount: 0 },
{ id: 2, name: 'Mouse', price: 25, inventory: 0, discount: 5 },
{ id: 3, name: 'Keyboard', price: 45, inventory: 10, discount: 10 },
],
};
productTraverser.traverseIn(
products,
['items.(*).({"value.inStock":true,"value.onSale":true})'],
[
({ value }) => {
console.log('Available on sale:', value.name);
},
]
);
Pattern Cheat Sheet
Basic Navigation
Pattern |
Matches |
users.name |
Direct property: data.users.name |
items.(*) |
All elements in data.items array |
products.(**) |
All nested properties under products |
categories.(*3*) |
Recursive search up to 3 levels deep |
("metadata") |
Property named "metadata" |
(id,name) |
Both "id" and "name" properties |
Condition Patterns
Pattern |
Matches |
({"key":"email"}) |
Properties with key "email" |
({"value.includes":"error"}) |
Values containing "error" |
({"key.includes":"date","value.isString":true}) |
Date properties with string values |
([{"value.<":100},{"value.>":1000}]) |
Values < 100 OR > 1000 |
Complex Examples
'users.(*).([{"key:equalWith":"status","value.equalWith":"active",},{"equalWith":"phone","value.exists":true}])';
'documents.(*3*).({"key.equalWith":"title","value.includes":"urgent"})';
'inventory.([{"category":"electronics"}]).prices.({"value.between":[10,100]})';
'(**).({"key.equalWith":"_id"}).setKey("id")';
Tips & Best Practices
- 1.Specificity First: Start patterns with specific keys before recursive descent
'users.(*).contacts.(**)';
'(**).contacts';
- 2.Combine Conditions: Use object conditions for AND, array conditions for OR
'({"value.isNumber":true,"value.>":10})';
'([{"status":"active"},{"priority.greaterThan":3}])';
- 3.Use Key/Value Specificity: Always specify key/value in conditions
'({"key.includes":"date","value.isString":true})';
'({"includes":"date"})';
- 4.Batch Operations: Process multiple patterns in single traversal
traverseIn(data, options, ['users.(*).name', 'users.(*).email'], [nameProcessor, emailProcessor]);
- 5.Depth Control: Limit recursion depth in large datasets
'largeDataset.(*4*)';
- 6.Path Utilization: Use objectPath for context-aware operations
callback: ({ value, objectPath }) => {
if (objectPath[objectPath.length - 1] === 'email') {
}
};
Limitations
- Circular References: Not supported (will cause infinite loops)
- Large Datasets: Deep recursion may impact performance
- Concurrent Modification: Changing structure during traversal may cause unexpected behavior
- Type Strictness: Conditions require explicit type handling
- Pattern Complexity: Highly complex patterns may have parsing overhead
Real-World Use Cases
- 1.Data Migration: Rename keys and transform values across complex structures
- 2.Validation: Verify data integrity in API payloads
- 3.Security Scans: Find sensitive data patterns (credit cards, tokens)
- 4.Data Cleaning: Normalize formats (phone numbers, emails)
- 5.Feature Flags: Conditionally modify configuration trees
- 6.Analytics: Extract specific metrics from complex event data
- 7.Schema Enforcement: Ensure data matches required structure
traverseIn(
userData,
{ injectedConditions },
[
'(**).({"key.includes":"email","value.isString":true})',
'(**).({"key.includes":"phone","value.isString":true})',
],
[
({ setValue }) => setValue('REDACTED'),
]
);
Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Submit a pull request
License
MIT License