km-traversal
Version:
This utility provides powerful object traversal capabilities with pattern matching and transformation features.
492 lines (397 loc) • 15.1 kB
Markdown
# **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
```bash
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
```typescript
// 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
): void
```
#### Adapter Pattern
```typescript
// code...
const { register } = adapter();
const traverser = register(customConditions);
traverser.traverseIn(data, patterns, callbacks);
```
#### Callback Parameters
```typescript
// 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
```typescript
// code...
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' } },
],
};
// Convert all emails to uppercase
traverseIn(
data,
{ injectedConditions: defaultConditions },
['users.(*).contact.email'],
[
({ value, setValue }) => {
setValue(value.toUpperCase());
},
]
);
```
#### Advanced Filtering
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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: 'john@example.com' },
{ id: 2, email: 'invalid' },
{ id: 3, email: 'jane@example.com' },
],
};
// 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
```typescript
// 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
```typescript
// 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
```typescript
// code...
// Good:
'users.(*).contacts.(**)';
// Less efficient:
'(**).contacts';
```
- 2.Combine Conditions: Use object conditions for AND, array conditions for OR
```typescript
// 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
```typescript
// code...
// Recommended:
'({"key.includes":"date","value.isString":true})';
// Avoid:
'({"includes":"date"})'; // Ambiguous
```
- 4.Batch Operations: Process multiple patterns in single traversal
```typescript
// code...
traverseIn(data, options, ['users.(*).name', 'users.(*).email'], [nameProcessor, emailProcessor]);
```
- 5.Depth Control: Limit recursion depth in large datasets
```typescript
// code...
// Limit to 4 levels deep
'largeDataset.(*4*)';
```
- 6.Path Utilization: Use objectPath for context-aware operations
```typescript
// 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
```typescript
// 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
## License
#### MIT License