km-traversal
v1.1.8
Published
This utility provides powerful object traversal capabilities with pattern matching and transformation features.
Downloads
39
Maintainers
Readme
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-traversalCore 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
// code...
traverseIn<ENTRY_DATA, OPTIONS>(
data: ENTRY_DATA, // Data to traverse (object or array)
options: ICustomEachOptions, // Configuration options
patterns: ( // Array of traversal patterns
string |
string[] |
(({setCondName}) => string)
)[],
callbacks: Callback[] // Functions to execute at target nodes
): voidAdapter Pattern
// code...
const { register } = adapter();
const traverser = register(customConditions);
traverser.traverseIn(data, patterns, callbacks);Callback Parameters
// code...
callback({
key: string | number, // Current key/index
value: any, // Current value
objectPath: (string | number)[], // Full path to node
parent: any, // Parent object/array
setKey: (newKey: string) => void, // Rename property
setValue: (newValue: any) => void, // Modify value
remove: () => void, // Delete Value
removeNears: () => void // Delete Near Values
})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
// code...
import { traverseIn, defaultConditions } from './traverseIn';
const data = {
users: [
{ id: 1, name: 'John', contact: { email: '[email protected]' } },
{ id: 2, name: 'Jane', contact: { email: '[email protected]' } },
],
};
// Convert all emails to uppercase
traverseIn(
data,
{ injectedConditions: defaultConditions },
['users.(*).contact.email'],
[
({ value, setValue }) => {
setValue(value.toUpperCase());
},
]
);Advanced Filtering
// code...
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'] },
],
};
// Find electronics under $1000
traverseIn(
productData,
{ injectedConditions: defaultConditions },
[
'inventory.([{ "key:equalWith":"price", "value.lessThan":1000},{ "key:equalWith":"tags", "value.arrayIncludes":"electronics"}])',
],
[
({ value }) => {
console.log('Filtered item:', value.title);
// Outputs: "Desk Lamp" and "Monitor"
},
]
);Key-Based Operations
// code...
const userData = {
user_list: [
{ user_id: 1, user_name: 'John' },
{ user_id: 2, user_name: 'Jane' },
],
};
// Rename user_id to id
traverseIn(
userData,
{ injectedConditions: defaultConditions },
['user_list.(*).user_id'],
[
({ setKey }) => {
setKey('id');
},
]
);
// Find keys containing "name"
traverseIn(
userData,
{ injectedConditions: defaultConditions },
['user_list.(*).({"key.includes":"name"})'],
[
({ value }) => {
console.log('Name property:', value);
},
]
);Recursive Search
// code...
const nestedData = {
a: {
b: {
c: 'Target 1',
d: { e: 'Target 2' },
},
f: { g: 'Target 3' },
},
};
// Find all string values starting with "Target"
traverseIn(
nestedData,
{ injectedConditions: defaultConditions },
[['(**)']],
[
({ value, objectPath }) => {
if (typeof value === 'string' && value.startsWith('Target')) {
console.log(`Found ${value} at: ${objectPath.join('.')}`);
}
},
]
);Custom Conditions
// code...
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: '[email protected]' },
{ id: 2, email: 'invalid' },
{ id: 3, email: '[email protected]' },
],
};
// Validate emails and even IDs
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
// code...
import { adapter } from './traverseIn';
// Create reusable traverser with custom conditions
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 },
],
};
// Find products that are in stock AND on sale
productTraverser.traverseIn(
products,
['items.(*).({"value.inStock":true,"value.onSale":true})'],
[
({ value }) => {
console.log('Available on sale:', value.name);
// Outputs: "Keyboard"
},
]
);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
// code...
// Find active users with phone numbers
'users.(*).([{"key:equalWith":"status","value.equalWith":"active",},{"equalWith":"phone","value.exists":true}])';
// Find titles containing "urgent" in first 3 levels
'documents.(*3*).({"key.equalWith":"title","value.includes":"urgent"})';
// Find prices between $10-$100 in electronics
'inventory.([{"category":"electronics"}]).prices.({"value.between":[10,100]})';
// Rename all _id properties to id
'(**).({"key.equalWith":"_id"}).setKey("id")';Tips & Best Practices
- 1.Specificity First: Start patterns with specific keys before recursive descent
// code...
// Good:
'users.(*).contacts.(**)';
// Less efficient:
'(**).contacts';- 2.Combine Conditions: Use object conditions for AND, array conditions for OR
// code...
// AND: Must satisfy all conditions
'({"value.isNumber":true,"value.>":10})';
// OR: Satisfy any condition set
'([{"status":"active"},{"priority.greaterThan":3}])';- 3.Use Key/Value Specificity: Always specify key/value in conditions
// code...
// Recommended:
'({"key.includes":"date","value.isString":true})';
// Avoid:
'({"includes":"date"})'; // Ambiguous- 4.Batch Operations: Process multiple patterns in single traversal
// code...
traverseIn(data, options, ['users.(*).name', 'users.(*).email'], [nameProcessor, emailProcessor]);- 5.Depth Control: Limit recursion depth in large datasets
// code...
// Limit to 4 levels deep
'largeDataset.(*4*)';- 6.Path Utilization: Use objectPath for context-aware operations
// code...
callback: ({ value, objectPath }) => {
if (objectPath[objectPath.length - 1] === 'email') {
// Special handling for email fields
}
};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
// code...
// Real-world example: GDPR compliance
traverseIn(
userData,
{ injectedConditions },
[
'(**).({"key.includes":"email","value.isString":true})',
'(**).({"key.includes":"phone","value.isString":true})',
],
[
// Anonymize PII data
({ 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
