tree-processor 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1026 -0
- package/README.md +201 -115
- package/dist/index.d.ts.map +1 -1
- package/dist/stats.html +1 -1
- package/package.json +1 -7
package/README.en.md
ADDED
|
@@ -0,0 +1,1026 @@
|
|
|
1
|
+
# tree-processor
|
|
2
|
+
|
|
3
|
+
<div align="right">
|
|
4
|
+
|
|
5
|
+
[English](https://github.com/knott11/tree-processor/blob/main/README.en.md) | [中文](https://github.com/knott11/tree-processor/blob/main/README.md)
|
|
6
|
+
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div align="center">
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
14
|
+

|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
A lightweight tree-structured data processing utility library written in TypeScript, supporting tree-shaking, with each format bundle size approximately **6-7 KB** (ESM: 6.39 KB, CJS: 6.64 KB, UMD: 6.68 KB).
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
## 📋 Table of Contents
|
|
23
|
+
|
|
24
|
+
- [Features](#-features)
|
|
25
|
+
- [Use Cases](#-use-cases)
|
|
26
|
+
- [Installation](#-installation)
|
|
27
|
+
- [Quick Start](#-quick-start)
|
|
28
|
+
- [API Documentation](#-api-documentation)
|
|
29
|
+
- [Traversal Methods](#traversal-methods)
|
|
30
|
+
- [Search Methods](#search-methods)
|
|
31
|
+
- [Access Methods](#access-methods)
|
|
32
|
+
- [Modification Methods](#modification-methods)
|
|
33
|
+
- [Query Methods](#query-methods)
|
|
34
|
+
- [Validation Methods](#validation-methods)
|
|
35
|
+
- [Custom Field Names](#custom-field-names)
|
|
36
|
+
- [Testing](#testing)
|
|
37
|
+
- [Development](#development)
|
|
38
|
+
|
|
39
|
+
## ✨ Features
|
|
40
|
+
|
|
41
|
+
- **Lightweight** - Each format bundle size is only 6-7 KB (ESM: 6.39 KB, CJS: 6.64 KB, UMD: 6.68 KB), minimal impact on project size
|
|
42
|
+
- **Tree-shaking Support** - Supports on-demand imports, only bundles the code you actually use, further reducing bundle size
|
|
43
|
+
- **Full TypeScript Support** - Provides complete type definitions and IntelliSense, improving development experience
|
|
44
|
+
- **Flexible Custom Field Names** - Supports custom children and id field names, adapting to various data structures
|
|
45
|
+
- **Zero Dependencies** - No external dependencies, ready to use out of the box, no need to worry about dependency conflicts
|
|
46
|
+
- **Comprehensive Test Coverage** - Contains 290 test cases with 99%+ test coverage (99.67% statement coverage, 99.32% branch coverage, 100% function coverage), covering basic functionality, edge cases, error handling, and complex scenarios
|
|
47
|
+
- **Rich API** - Provides 30+ methods, including array-like APIs (map, filter, find, some, every, includes, at, indexOf, etc.), and tree-specific operations (get parent/child nodes, depth calculation, data validation, etc.), covering complete scenarios for traversal, search, modification, and validation
|
|
48
|
+
|
|
49
|
+
**Supported methods:** mapTree, forEachTree, filterTree, findTree, pushTree, unshiftTree, popTree, shiftTree, someTree, everyTree, includesTree, atTree, indexOfTree, atIndexOfTree, dedupTree, removeTree, getParentTree, getChildrenTree, getSiblingsTree, getNodeDepthMap, getNodeDepth, isLeafNode, isRootNode, isEmptyTreeData, isEmptySingleTreeData, isTreeData, isSingleTreeData, isValidTreeNode, isTreeNodeWithCircularCheck, isSafeTreeDepth. The last parameter of each method can customize the property names for children and id.
|
|
50
|
+
|
|
51
|
+
### 💡 Use Cases
|
|
52
|
+
|
|
53
|
+
- **Navigation Systems** - Multi-level menus, route configuration expansion, collapse, search, and filtering
|
|
54
|
+
- **File Systems** - File directory traversal, search, move, and delete
|
|
55
|
+
- **Permission Systems** - Organizational structure, role permission tree structure management and validation
|
|
56
|
+
- **Framework Development** - Component trees, route trees, and other tree structure construction and management
|
|
57
|
+
- **Data Management** - Category management, comment systems, tree selectors, and other data operations
|
|
58
|
+
|
|
59
|
+
## 📦 Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install tree-processor
|
|
63
|
+
# or
|
|
64
|
+
yarn add tree-processor
|
|
65
|
+
# or
|
|
66
|
+
pnpm add tree-processor
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 🚀 Quick Start
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
import { mapTree, findTree, filterTree } from 'tree-processor'
|
|
73
|
+
|
|
74
|
+
const treeData = [
|
|
75
|
+
{
|
|
76
|
+
id: 1,
|
|
77
|
+
name: 'node1',
|
|
78
|
+
children: [
|
|
79
|
+
{ id: 2, name: 'node2' },
|
|
80
|
+
{ id: 3, name: 'node3' },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
// Get all node names
|
|
86
|
+
const names = mapTree(treeData, (node) => node.name)
|
|
87
|
+
console.log(names) // ['node1', 'node2', 'node3']
|
|
88
|
+
|
|
89
|
+
// Find a node
|
|
90
|
+
const node = findTree(treeData, (n) => n.id === 2)
|
|
91
|
+
console.log(node) // { id: 2, name: 'node2' }
|
|
92
|
+
|
|
93
|
+
// Filter nodes
|
|
94
|
+
const filtered = filterTree(treeData, (n) => n.id > 1)
|
|
95
|
+
console.log(filtered) // [{ id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 📖 API Documentation
|
|
99
|
+
|
|
100
|
+
### Import Methods
|
|
101
|
+
|
|
102
|
+
#### Default Import (Recommended for scenarios requiring multiple methods)
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// ES Module
|
|
106
|
+
import t from 'tree-processor'
|
|
107
|
+
|
|
108
|
+
// CommonJS
|
|
109
|
+
const t = require('tree-processor')
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Named Import (Recommended for scenarios requiring only a few methods, supports tree-shaking)
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
// ES Module - Named import of individual methods
|
|
116
|
+
import { mapTree, filterTree, findTree } from 'tree-processor'
|
|
117
|
+
|
|
118
|
+
// ES Module - Import types
|
|
119
|
+
import type { TreeNode, TreeData, FieldNames } from 'tree-processor'
|
|
120
|
+
|
|
121
|
+
// CommonJS - Named import
|
|
122
|
+
const { mapTree, filterTree } = require('tree-processor')
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Sample Data
|
|
126
|
+
|
|
127
|
+
The following sample data will be used for demonstrations of all subsequent methods:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
const treeData = [
|
|
131
|
+
{
|
|
132
|
+
id: 1,
|
|
133
|
+
name: 'node1',
|
|
134
|
+
children: [
|
|
135
|
+
{
|
|
136
|
+
id: 2,
|
|
137
|
+
name: 'node2',
|
|
138
|
+
children: [
|
|
139
|
+
{ id: 4, name: 'node4' },
|
|
140
|
+
{ id: 5, name: 'node5' },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 3,
|
|
145
|
+
name: 'node3',
|
|
146
|
+
children: [{ id: 6, name: 'node6' }],
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Traversal Methods
|
|
156
|
+
|
|
157
|
+
### mapTree
|
|
158
|
+
|
|
159
|
+
Traverse tree-structured data and execute a callback function for each node, returning a mapped array.
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
// Get all node names
|
|
163
|
+
const nodeNames = t.mapTree(treeData, (node) => node.name)
|
|
164
|
+
console.log(nodeNames) // ['node1', 'node2', 'node4', 'node5', 'node3', 'node6']
|
|
165
|
+
|
|
166
|
+
// Get all node IDs
|
|
167
|
+
const nodeIds = t.mapTree(treeData, (node) => node.id)
|
|
168
|
+
console.log(nodeIds) // [1, 2, 4, 5, 3, 6]
|
|
169
|
+
|
|
170
|
+
// Modify node data
|
|
171
|
+
const modifiedNodes = t.mapTree(treeData, (node) => ({
|
|
172
|
+
...node,
|
|
173
|
+
label: node.name
|
|
174
|
+
}))
|
|
175
|
+
console.log(modifiedNodes) // Returns a new array containing the label field
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### forEachTree
|
|
179
|
+
|
|
180
|
+
Traverse tree-structured data and execute a callback function for each node. Unlike mapTree, this doesn't return a value and has better performance, suitable for scenarios where you only need to traverse without returning results.
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// Traverse all nodes and print
|
|
184
|
+
t.forEachTree(treeData, (node) => {
|
|
185
|
+
console.log(node)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
// Modify node properties
|
|
189
|
+
t.forEachTree(treeData, (node) => {
|
|
190
|
+
node.visited = true
|
|
191
|
+
node.timestamp = Date.now()
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Count nodes
|
|
195
|
+
let nodeCount = 0
|
|
196
|
+
t.forEachTree(treeData, () => {
|
|
197
|
+
nodeCount++
|
|
198
|
+
})
|
|
199
|
+
console.log(nodeCount) // Total number of nodes
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Search Methods
|
|
205
|
+
|
|
206
|
+
### filterTree
|
|
207
|
+
|
|
208
|
+
Filter tree-structured data and return nodes that meet the conditions.
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
// Filter nodes whose names contain 'node'
|
|
212
|
+
const filteredNodes = t.filterTree(treeData, (node) => {
|
|
213
|
+
return node.name.includes('node')
|
|
214
|
+
})
|
|
215
|
+
console.log(filteredNodes) // Returns an array of nodes that meet the conditions
|
|
216
|
+
|
|
217
|
+
// Filter nodes with ID greater than 2
|
|
218
|
+
const nodesWithLargeId = t.filterTree(treeData, (node) => node.id > 2)
|
|
219
|
+
console.log(nodesWithLargeId) // Returns an array of nodes with ID greater than 2
|
|
220
|
+
|
|
221
|
+
// Filter nodes without children (leaf nodes)
|
|
222
|
+
const leafNodes = t.filterTree(treeData, (node) => {
|
|
223
|
+
return !node.children || node.children.length === 0
|
|
224
|
+
})
|
|
225
|
+
console.log(leafNodes) // Returns all leaf nodes
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### findTree
|
|
229
|
+
|
|
230
|
+
Find the first node in tree-structured data that meets the condition. Returns null if not found.
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
// Find node with ID 2
|
|
234
|
+
const foundNode = t.findTree(treeData, (node) => node.id === 2)
|
|
235
|
+
console.log(foundNode) // Returns the found node object, or null if not found
|
|
236
|
+
|
|
237
|
+
// Find node with name 'node3'
|
|
238
|
+
const node3 = t.findTree(treeData, (node) => node.name === 'node3')
|
|
239
|
+
console.log(node3) // { id: 3, name: 'node3', children: [...] }
|
|
240
|
+
|
|
241
|
+
// Find a non-existent node
|
|
242
|
+
const nodeNotFound = t.findTree(treeData, (node) => node.id === 999)
|
|
243
|
+
console.log(nodeNotFound) // null
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### includesTree
|
|
247
|
+
|
|
248
|
+
Check if tree-structured data contains a node with the specified ID.
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
const nodeId = 2
|
|
252
|
+
const hasNode = t.includesTree(treeData, nodeId)
|
|
253
|
+
|
|
254
|
+
console.log(hasNode) // true means it contains the node, false means it doesn't
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### someTree
|
|
258
|
+
|
|
259
|
+
Check if there exists a node in tree-structured data that meets the condition. Returns true if at least one node meets the condition.
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
// Check if there exists a node with name 'node2'
|
|
263
|
+
const hasNode2 = t.someTree(treeData, node => node.name === 'node2')
|
|
264
|
+
console.log(hasNode2) // true
|
|
265
|
+
|
|
266
|
+
// Check if there exists a node with ID greater than 10
|
|
267
|
+
const hasLargeId = t.someTree(treeData, node => node.id > 10)
|
|
268
|
+
console.log(hasLargeId) // false
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### everyTree
|
|
272
|
+
|
|
273
|
+
Check if all nodes in tree-structured data meet the condition. Returns true only if all nodes meet the condition.
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
// Check if all node IDs are greater than 0
|
|
277
|
+
const allIdsPositive = t.everyTree(treeData, node => node.id > 0)
|
|
278
|
+
console.log(allIdsPositive) // true
|
|
279
|
+
|
|
280
|
+
// Check if all nodes have a name property
|
|
281
|
+
const allHaveName = t.everyTree(treeData, node => node.name)
|
|
282
|
+
console.log(allHaveName) // Returns true or false based on actual data
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Access Methods
|
|
288
|
+
|
|
289
|
+
### atTree
|
|
290
|
+
|
|
291
|
+
Get a node by parent node ID and child node index. Supports negative indices, same as the array at method. Returns null if not found.
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
// Get the first child node (index 0) of the node with ID 1
|
|
295
|
+
const firstChildNode = t.atTree(treeData, 1, 0)
|
|
296
|
+
console.log(firstChildNode) // Returns the first child node
|
|
297
|
+
|
|
298
|
+
// Get the last child node (negative index)
|
|
299
|
+
const lastChildNode = t.atTree(treeData, 1, -1)
|
|
300
|
+
console.log(lastChildNode) // Returns the last child node
|
|
301
|
+
|
|
302
|
+
// Index out of range returns null
|
|
303
|
+
const nodeNotFound = t.atTree(treeData, 1, 10)
|
|
304
|
+
console.log(nodeNotFound) // null
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### indexOfTree
|
|
308
|
+
|
|
309
|
+
Returns an array representing the index path from the root node to the node with the targetId. Returns null if not found. The return value can be passed to the second parameter of atIndexOfTree for value retrieval.
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
// Get the index path of the node with ID 4
|
|
313
|
+
const nodePath = t.indexOfTree(treeData, 4)
|
|
314
|
+
console.log(nodePath) // [0, 0, 0] means root node -> first child -> first child
|
|
315
|
+
|
|
316
|
+
// Node not found returns null
|
|
317
|
+
const pathNotFound = t.indexOfTree(treeData, 999)
|
|
318
|
+
console.log(pathNotFound) // null
|
|
319
|
+
|
|
320
|
+
// Use with atIndexOfTree
|
|
321
|
+
const indexPath = t.indexOfTree(treeData, 4)
|
|
322
|
+
const nodeByPath = t.atIndexOfTree(treeData, indexPath)
|
|
323
|
+
console.log(nodeByPath) // Gets the node with ID 4
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### atIndexOfTree
|
|
327
|
+
|
|
328
|
+
Get a node by index path. Returns null if the path is invalid or out of range.
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
// Get node by index path
|
|
332
|
+
const nodeByIndexPath = t.atIndexOfTree(treeData, [0, 1, 0])
|
|
333
|
+
console.log(nodeByIndexPath) // Returns the node object at the corresponding path
|
|
334
|
+
|
|
335
|
+
// Use with indexOfTree
|
|
336
|
+
const targetPath = t.indexOfTree(treeData, 4)
|
|
337
|
+
const targetNode = t.atIndexOfTree(treeData, targetPath)
|
|
338
|
+
console.log(targetNode) // Gets the node with ID 4
|
|
339
|
+
|
|
340
|
+
// Invalid path returns null
|
|
341
|
+
const invalidPath = t.atIndexOfTree(treeData, [999])
|
|
342
|
+
console.log(invalidPath) // null
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Modification Methods
|
|
348
|
+
|
|
349
|
+
### pushTree
|
|
350
|
+
|
|
351
|
+
Add a child node to the end under the specified node. Returns true if successful, false if the target node is not found.
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
// Add a new child node under the node with ID 1
|
|
355
|
+
const addSuccess = t.pushTree(treeData, 1, { id: 7, name: 'node7' })
|
|
356
|
+
console.log(addSuccess) // true
|
|
357
|
+
console.log(treeData) // New node has been added to the end of the children array
|
|
358
|
+
|
|
359
|
+
// Try to add under a non-existent node
|
|
360
|
+
const addFailed = t.pushTree(treeData, 999, { id: 8, name: 'node8' })
|
|
361
|
+
console.log(addFailed) // false, target node not found
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### unshiftTree
|
|
365
|
+
|
|
366
|
+
Add a child node to the beginning under the specified node. Returns true if successful, false if the target node is not found.
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
// Add a new child node to the beginning under the node with ID 1
|
|
370
|
+
const unshiftSuccess = t.unshiftTree(treeData, 1, { id: 7, name: 'node7' })
|
|
371
|
+
console.log(unshiftSuccess) // true
|
|
372
|
+
console.log(treeData) // New node has been added to the beginning of the children array
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### popTree
|
|
376
|
+
|
|
377
|
+
Remove the last child node under the specified node. Returns the removed node, or false if the node doesn't exist or has no children.
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
// Remove the last child node under the node with ID 1
|
|
381
|
+
const removedNode = t.popTree(treeData, 1)
|
|
382
|
+
console.log(removedNode) // Returns the removed node object, or false
|
|
383
|
+
|
|
384
|
+
// Try to remove from a non-existent node
|
|
385
|
+
const popFailed = t.popTree(treeData, 999)
|
|
386
|
+
console.log(popFailed) // false
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### shiftTree
|
|
390
|
+
|
|
391
|
+
Remove the first child node under the specified node. Returns the removed node, or false if the node doesn't exist or has no children.
|
|
392
|
+
|
|
393
|
+
```javascript
|
|
394
|
+
// Remove the first child node under the node with ID 1
|
|
395
|
+
const shiftedNode = t.shiftTree(treeData, 1)
|
|
396
|
+
console.log(shiftedNode) // Returns the removed node object, or false
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### removeTree
|
|
400
|
+
|
|
401
|
+
Remove the node with the specified ID from tree-structured data, including root nodes and child nodes.
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
const nodeIdToRemove = 2
|
|
405
|
+
const removeSuccess = t.removeTree(treeData, nodeIdToRemove)
|
|
406
|
+
|
|
407
|
+
console.log(removeSuccess) // true means successful removal, false means node not found
|
|
408
|
+
console.log(treeData) // Tree structure after removal
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### dedupTree
|
|
412
|
+
|
|
413
|
+
Tree-structured object array deduplication method that removes duplicate nodes based on the specified key. Keeps the first occurrence of the node.
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
// Deduplicate based on id field
|
|
417
|
+
const uniqueTreeData = t.dedupTree(treeData, 'id')
|
|
418
|
+
console.log(uniqueTreeData) // Returns deduplicated tree-structured data
|
|
419
|
+
|
|
420
|
+
// Deduplicate based on name field
|
|
421
|
+
const uniqueByNameTree = t.dedupTree(treeData, 'name')
|
|
422
|
+
console.log(uniqueByNameTree) // Returns data deduplicated by name
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Query Methods
|
|
428
|
+
|
|
429
|
+
### getParentTree
|
|
430
|
+
|
|
431
|
+
Get the parent node of the specified node. Returns null if the node is a root node or not found.
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
// Get the parent node of the node with ID 2
|
|
435
|
+
const parentNode = t.getParentTree(treeData, 2)
|
|
436
|
+
console.log(parentNode) // Returns the parent node object { id: 1, name: 'node1', ... }
|
|
437
|
+
|
|
438
|
+
// Root nodes have no parent, returns null
|
|
439
|
+
const rootParentNode = t.getParentTree(treeData, 1)
|
|
440
|
+
console.log(rootParentNode) // null
|
|
441
|
+
|
|
442
|
+
// Node not found returns null
|
|
443
|
+
const parentNotFound = t.getParentTree(treeData, 999)
|
|
444
|
+
console.log(parentNotFound) // null
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### getChildrenTree
|
|
448
|
+
|
|
449
|
+
Get all direct child nodes of the specified node. Returns an empty array if the node is not found or has no children.
|
|
450
|
+
|
|
451
|
+
```javascript
|
|
452
|
+
// Get all child nodes of the node with ID 1
|
|
453
|
+
const children = t.getChildrenTree(treeData, 1)
|
|
454
|
+
console.log(children) // Returns child node array [{ id: 2, ... }, { id: 3, ... }]
|
|
455
|
+
|
|
456
|
+
// Node has no children, returns empty array
|
|
457
|
+
const emptyChildren = t.getChildrenTree(treeData, 4)
|
|
458
|
+
console.log(emptyChildren) // []
|
|
459
|
+
|
|
460
|
+
// Node not found returns empty array
|
|
461
|
+
const notFound = t.getChildrenTree(treeData, 999)
|
|
462
|
+
console.log(notFound) // []
|
|
463
|
+
|
|
464
|
+
// Support custom field names
|
|
465
|
+
const customTree = [
|
|
466
|
+
{
|
|
467
|
+
nodeId: 1,
|
|
468
|
+
name: 'root',
|
|
469
|
+
subNodes: [
|
|
470
|
+
{ nodeId: 2, name: 'child1' },
|
|
471
|
+
{ nodeId: 3, name: 'child2' },
|
|
472
|
+
],
|
|
473
|
+
},
|
|
474
|
+
];
|
|
475
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
476
|
+
const customChildren = t.getChildrenTree(customTree, 1, fieldNames)
|
|
477
|
+
console.log(customChildren) // Returns child node array
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### getSiblingsTree
|
|
481
|
+
|
|
482
|
+
Get all sibling nodes of the specified node (including itself). Returns an empty array if the node is not found. Sibling nodes of root nodes are other root nodes.
|
|
483
|
+
|
|
484
|
+
```javascript
|
|
485
|
+
// Get all sibling nodes (including itself) of the node with ID 2
|
|
486
|
+
const siblings = t.getSiblingsTree(treeData, 2)
|
|
487
|
+
console.log(siblings) // Returns sibling node array [{ id: 2, ... }, { id: 3, ... }]
|
|
488
|
+
|
|
489
|
+
// Sibling nodes of root nodes are other root nodes
|
|
490
|
+
const multiRoot = [
|
|
491
|
+
{ id: 1, children: [{ id: 2 }] },
|
|
492
|
+
{ id: 3, children: [{ id: 4 }] },
|
|
493
|
+
];
|
|
494
|
+
const rootSiblings = t.getSiblingsTree(multiRoot, 1)
|
|
495
|
+
console.log(rootSiblings) // Returns all root nodes [{ id: 1, ... }, { id: 3, ... }]
|
|
496
|
+
|
|
497
|
+
// Node not found returns empty array
|
|
498
|
+
const notFound = t.getSiblingsTree(treeData, 999)
|
|
499
|
+
console.log(notFound) // []
|
|
500
|
+
|
|
501
|
+
// Support custom field names
|
|
502
|
+
const customTree = [
|
|
503
|
+
{
|
|
504
|
+
nodeId: 1,
|
|
505
|
+
name: 'root',
|
|
506
|
+
subNodes: [
|
|
507
|
+
{ nodeId: 2, name: 'child1' },
|
|
508
|
+
{ nodeId: 3, name: 'child2' },
|
|
509
|
+
{ nodeId: 4, name: 'child3' },
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
];
|
|
513
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
514
|
+
const customSiblings = t.getSiblingsTree(customTree, 2, fieldNames)
|
|
515
|
+
console.log(customSiblings) // Returns sibling node array (including itself)
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### getNodeDepthMap
|
|
519
|
+
|
|
520
|
+
Returns a dictionary where keys represent node ids and values represent which layer the node is in the data. Depth starts from 1, root nodes have depth 1.
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
// Get depth map of all nodes
|
|
524
|
+
const nodeDepthMap = t.getNodeDepthMap(treeData)
|
|
525
|
+
console.log(nodeDepthMap) // { 1: 1, 2: 2, 3: 2, 4: 3, 5: 3, 6: 3 }
|
|
526
|
+
|
|
527
|
+
// Get depth of a specific node
|
|
528
|
+
const node2Depth = nodeDepthMap[2]
|
|
529
|
+
console.log(node2Depth) // 2
|
|
530
|
+
|
|
531
|
+
// Empty tree returns empty object
|
|
532
|
+
const emptyDepthMap = t.getNodeDepthMap([])
|
|
533
|
+
console.log(emptyDepthMap) // {}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### getNodeDepth
|
|
537
|
+
|
|
538
|
+
Get the depth of the specified node. Depth starts from 1, root nodes have depth 1.
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
// Get depth of root node
|
|
542
|
+
const rootDepth = t.getNodeDepth(treeData, 1)
|
|
543
|
+
console.log(rootDepth) // 1
|
|
544
|
+
|
|
545
|
+
// Get depth of child node
|
|
546
|
+
const childDepth = t.getNodeDepth(treeData, 2)
|
|
547
|
+
console.log(childDepth) // 2
|
|
548
|
+
|
|
549
|
+
// Get depth of deep node
|
|
550
|
+
const deepDepth = t.getNodeDepth(treeData, 4)
|
|
551
|
+
console.log(deepDepth) // 3
|
|
552
|
+
|
|
553
|
+
// Node not found returns null
|
|
554
|
+
const notFound = t.getNodeDepth(treeData, 999)
|
|
555
|
+
console.log(notFound) // null
|
|
556
|
+
|
|
557
|
+
// Support custom field names
|
|
558
|
+
const customTree = [
|
|
559
|
+
{
|
|
560
|
+
nodeId: 1,
|
|
561
|
+
name: 'root',
|
|
562
|
+
subNodes: [
|
|
563
|
+
{ nodeId: 2, name: 'child' },
|
|
564
|
+
],
|
|
565
|
+
},
|
|
566
|
+
];
|
|
567
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
568
|
+
const depth = t.getNodeDepth(customTree, 2, fieldNames)
|
|
569
|
+
console.log(depth) // 2
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**Difference from getNodeDepthMap:**
|
|
573
|
+
- `getNodeDepthMap` - Batch get depth of all nodes (calculates all nodes at once)
|
|
574
|
+
- `getNodeDepth` - Only get depth of a single node (only calculates the target node, more efficient)
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Validation Methods
|
|
579
|
+
|
|
580
|
+
### isLeafNode
|
|
581
|
+
|
|
582
|
+
Check if a node is a leaf node (has no children). Lightweight method that only checks the node itself, doesn't traverse the tree.
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
// Nodes without children field are leaf nodes
|
|
586
|
+
const leafNode1 = { id: 1, name: 'node1' };
|
|
587
|
+
console.log(t.isLeafNode(leafNode1)) // true
|
|
588
|
+
|
|
589
|
+
// Nodes with empty children array are leaf nodes
|
|
590
|
+
const leafNode2 = { id: 2, name: 'node2', children: [] };
|
|
591
|
+
console.log(t.isLeafNode(leafNode2)) // true
|
|
592
|
+
|
|
593
|
+
// Nodes with children are not leaf nodes
|
|
594
|
+
const parentNode = {
|
|
595
|
+
id: 3,
|
|
596
|
+
name: 'node3',
|
|
597
|
+
children: [{ id: 4, name: 'node4' }],
|
|
598
|
+
};
|
|
599
|
+
console.log(t.isLeafNode(parentNode)) // false
|
|
600
|
+
|
|
601
|
+
// Use in filterTree (filter out all leaf nodes)
|
|
602
|
+
const leafNodes = t.filterTree(treeData, (node) => t.isLeafNode(node))
|
|
603
|
+
console.log(leafNodes) // Returns all leaf nodes
|
|
604
|
+
|
|
605
|
+
// Use in forEachTree
|
|
606
|
+
t.forEachTree(treeData, (node) => {
|
|
607
|
+
if (t.isLeafNode(node)) {
|
|
608
|
+
console.log('Leaf node:', node.name)
|
|
609
|
+
}
|
|
610
|
+
})
|
|
611
|
+
|
|
612
|
+
// Support custom field names
|
|
613
|
+
const customNode = {
|
|
614
|
+
nodeId: 1,
|
|
615
|
+
name: 'node1',
|
|
616
|
+
subNodes: [],
|
|
617
|
+
};
|
|
618
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
619
|
+
console.log(t.isLeafNode(customNode, fieldNames)) // true
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**Difference from existing methods:**
|
|
623
|
+
- `isLeafNode` - Only checks a single node, lightweight (O(1)), suitable for use during traversal
|
|
624
|
+
- `getChildrenTree` - Gets child node array, requires passing tree and nodeId, needs to find the node (O(n))
|
|
625
|
+
|
|
626
|
+
### isRootNode
|
|
627
|
+
|
|
628
|
+
Check if a node is a root node (has no parent). Root nodes are top-level nodes in the tree-structured data array.
|
|
629
|
+
|
|
630
|
+
```javascript
|
|
631
|
+
// Check root node
|
|
632
|
+
const treeData = [
|
|
633
|
+
{
|
|
634
|
+
id: 1,
|
|
635
|
+
name: 'root1',
|
|
636
|
+
children: [{ id: 2, name: 'child1' }],
|
|
637
|
+
},
|
|
638
|
+
];
|
|
639
|
+
console.log(t.isRootNode(treeData, 1)) // true
|
|
640
|
+
console.log(t.isRootNode(treeData, 2)) // false
|
|
641
|
+
|
|
642
|
+
// Multiple root nodes case
|
|
643
|
+
const multiRoot = [
|
|
644
|
+
{ id: 1, name: 'root1' },
|
|
645
|
+
{ id: 2, name: 'root2' },
|
|
646
|
+
{ id: 3, name: 'root3' },
|
|
647
|
+
];
|
|
648
|
+
console.log(t.isRootNode(multiRoot, 1)) // true
|
|
649
|
+
console.log(t.isRootNode(multiRoot, 2)) // true
|
|
650
|
+
console.log(t.isRootNode(multiRoot, 3)) // true
|
|
651
|
+
|
|
652
|
+
// Use during traversal
|
|
653
|
+
t.forEachTree(treeData, (node) => {
|
|
654
|
+
if (t.isRootNode(treeData, node.id)) {
|
|
655
|
+
console.log('Root node:', node.name)
|
|
656
|
+
}
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
// Support custom field names
|
|
660
|
+
const customTree = [
|
|
661
|
+
{
|
|
662
|
+
nodeId: 1,
|
|
663
|
+
name: 'root1',
|
|
664
|
+
subNodes: [{ nodeId: 2, name: 'child1' }],
|
|
665
|
+
},
|
|
666
|
+
];
|
|
667
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
668
|
+
console.log(t.isRootNode(customTree, 1, fieldNames)) // true
|
|
669
|
+
console.log(t.isRootNode(customTree, 2, fieldNames)) // false
|
|
670
|
+
|
|
671
|
+
// Returns false when node doesn't exist
|
|
672
|
+
console.log(t.isRootNode(treeData, 999)) // false
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
**Difference from existing methods:**
|
|
676
|
+
- `isRootNode` - Semantic method that directly returns boolean value
|
|
677
|
+
- `getParentTree` - Returns parent node object, need to check if it's null
|
|
678
|
+
- `getNodeDepth` - Returns depth, need to check if it equals 1
|
|
679
|
+
|
|
680
|
+
### isEmptyTreeData
|
|
681
|
+
|
|
682
|
+
Check if tree-structured data (array) is empty. Empty arrays, null, and undefined are all considered empty. This function supports the fieldNames parameter to maintain API consistency, but the parameter has no effect (because it only checks if the array is empty, doesn't access children or id fields).
|
|
683
|
+
|
|
684
|
+
```javascript
|
|
685
|
+
// Check if tree-structured data is empty
|
|
686
|
+
const isEmptyTree = t.isEmptyTreeData(treeData)
|
|
687
|
+
console.log(isEmptyTree) // false (has data)
|
|
688
|
+
|
|
689
|
+
// Empty array returns true
|
|
690
|
+
const isEmptyArray = t.isEmptyTreeData([])
|
|
691
|
+
console.log(isEmptyArray) // true
|
|
692
|
+
|
|
693
|
+
// null or undefined returns true
|
|
694
|
+
const isNullTree = t.isEmptyTreeData(null)
|
|
695
|
+
console.log(isNullTree) // true
|
|
696
|
+
|
|
697
|
+
// Support fieldNames parameter (maintains API consistency, but has no effect)
|
|
698
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
699
|
+
const isEmptyWithFieldNames = t.isEmptyTreeData(treeData, fieldNames)
|
|
700
|
+
console.log(isEmptyWithFieldNames) // false (same result as not passing fieldNames)
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### isEmptySingleTreeData
|
|
704
|
+
|
|
705
|
+
Check if a single tree-structured data is empty. If the data is not valid single tree-structured data, has no children field, or children is an empty array, it is considered empty. If there are child nodes (children array is not empty), even if the child nodes themselves are empty, the tree is not empty.
|
|
706
|
+
|
|
707
|
+
```javascript
|
|
708
|
+
// No children field, considered empty
|
|
709
|
+
const tree1 = { id: 1, name: 'node1' };
|
|
710
|
+
const isEmpty1 = t.isEmptySingleTreeData(tree1)
|
|
711
|
+
console.log(isEmpty1) // true
|
|
712
|
+
|
|
713
|
+
// children is empty array, considered empty
|
|
714
|
+
const tree2 = {
|
|
715
|
+
id: 1,
|
|
716
|
+
name: 'node1',
|
|
717
|
+
children: [],
|
|
718
|
+
};
|
|
719
|
+
const isEmpty2 = t.isEmptySingleTreeData(tree2)
|
|
720
|
+
console.log(isEmpty2) // true
|
|
721
|
+
|
|
722
|
+
// Has child nodes, not empty
|
|
723
|
+
const tree3 = {
|
|
724
|
+
id: 1,
|
|
725
|
+
name: 'node1',
|
|
726
|
+
children: [
|
|
727
|
+
{ id: 2, name: 'node2' },
|
|
728
|
+
],
|
|
729
|
+
};
|
|
730
|
+
const isEmpty3 = t.isEmptySingleTreeData(tree3)
|
|
731
|
+
console.log(isEmpty3) // false
|
|
732
|
+
|
|
733
|
+
// Has child nodes, even if child nodes themselves are empty, tree is not empty
|
|
734
|
+
const tree4 = {
|
|
735
|
+
id: 1,
|
|
736
|
+
name: 'node1',
|
|
737
|
+
children: [
|
|
738
|
+
{ id: 2, name: 'node2', children: [] },
|
|
739
|
+
{ id: 3, name: 'node3' }, // no children field
|
|
740
|
+
],
|
|
741
|
+
};
|
|
742
|
+
const isEmpty4 = t.isEmptySingleTreeData(tree4)
|
|
743
|
+
console.log(isEmpty4) // false (because there are child nodes, even if they are empty)
|
|
744
|
+
|
|
745
|
+
// Support custom field names
|
|
746
|
+
const customTree = {
|
|
747
|
+
nodeId: 1,
|
|
748
|
+
name: 'node1',
|
|
749
|
+
subNodes: [],
|
|
750
|
+
};
|
|
751
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
752
|
+
const isEmptyCustom = t.isEmptySingleTreeData(customTree, fieldNames)
|
|
753
|
+
console.log(isEmptyCustom) // true
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### isTreeData
|
|
757
|
+
|
|
758
|
+
Check if data is tree-structured data (array). Tree-structured data must be an array, and each element in the array must be valid single tree-structured data.
|
|
759
|
+
|
|
760
|
+
```javascript
|
|
761
|
+
// Valid tree-structured data (forest)
|
|
762
|
+
const forest = [
|
|
763
|
+
{
|
|
764
|
+
id: 1,
|
|
765
|
+
name: 'node1',
|
|
766
|
+
children: [{ id: 2, name: 'node2' }],
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
id: 3,
|
|
770
|
+
name: 'node3',
|
|
771
|
+
children: [{ id: 4, name: 'node4' }],
|
|
772
|
+
},
|
|
773
|
+
];
|
|
774
|
+
console.log(t.isTreeData(forest)) // true
|
|
775
|
+
|
|
776
|
+
// Empty array is also valid tree-structured data (empty forest)
|
|
777
|
+
console.log(t.isTreeData([])) // true
|
|
778
|
+
|
|
779
|
+
// Single object is not tree-structured data (should use isSingleTreeData)
|
|
780
|
+
console.log(t.isTreeData({ id: 1 })) // false
|
|
781
|
+
|
|
782
|
+
// Array contains non-tree-structured elements, returns false
|
|
783
|
+
const invalidForest = [
|
|
784
|
+
{ id: 1, children: [{ id: 2 }] },
|
|
785
|
+
'not a tree', // invalid element
|
|
786
|
+
];
|
|
787
|
+
console.log(t.isTreeData(invalidForest)) // false
|
|
788
|
+
|
|
789
|
+
// null or undefined are not valid tree-structured data
|
|
790
|
+
console.log(t.isTreeData(null)) // false
|
|
791
|
+
console.log(t.isTreeData(undefined)) // false
|
|
792
|
+
|
|
793
|
+
// Support custom field names
|
|
794
|
+
const customForest = [
|
|
795
|
+
{
|
|
796
|
+
nodeId: 1,
|
|
797
|
+
name: 'node1',
|
|
798
|
+
subNodes: [{ nodeId: 2, name: 'node2' }],
|
|
799
|
+
},
|
|
800
|
+
];
|
|
801
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
802
|
+
console.log(t.isTreeData(customForest, fieldNames)) // true
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### isSingleTreeData
|
|
806
|
+
|
|
807
|
+
Check if data is single tree-structured data (single object). Tree-structured data must be an object (cannot be an array, null, undefined, or primitive type). If a children field exists, it must be an array type, and all child nodes will be recursively checked.
|
|
808
|
+
|
|
809
|
+
```javascript
|
|
810
|
+
// Valid single tree-structured data
|
|
811
|
+
const tree = {
|
|
812
|
+
id: 1,
|
|
813
|
+
name: 'node1',
|
|
814
|
+
children: [
|
|
815
|
+
{ id: 2, name: 'node2' },
|
|
816
|
+
{ id: 3, name: 'node3' },
|
|
817
|
+
],
|
|
818
|
+
};
|
|
819
|
+
const isValid = t.isSingleTreeData(tree)
|
|
820
|
+
console.log(isValid) // true
|
|
821
|
+
|
|
822
|
+
// No children field is also valid (only root node)
|
|
823
|
+
const singleNode = { id: 1, name: 'node1' }
|
|
824
|
+
console.log(t.isSingleTreeData(singleNode)) // true
|
|
825
|
+
|
|
826
|
+
// Array is not single tree-structured data
|
|
827
|
+
console.log(t.isSingleTreeData([])) // false
|
|
828
|
+
|
|
829
|
+
// null or undefined are not valid tree-structured data
|
|
830
|
+
console.log(t.isSingleTreeData(null)) // false
|
|
831
|
+
console.log(t.isSingleTreeData(undefined)) // false
|
|
832
|
+
|
|
833
|
+
// children cannot be null
|
|
834
|
+
const invalidTree = { id: 1, children: null }
|
|
835
|
+
console.log(t.isSingleTreeData(invalidTree)) // false
|
|
836
|
+
|
|
837
|
+
// Support custom field names
|
|
838
|
+
const customTree = {
|
|
839
|
+
nodeId: 1,
|
|
840
|
+
name: 'node1',
|
|
841
|
+
subNodes: [{ nodeId: 2, name: 'node2' }],
|
|
842
|
+
};
|
|
843
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
844
|
+
console.log(t.isSingleTreeData(customTree, fieldNames)) // true
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
### isValidTreeNode
|
|
848
|
+
|
|
849
|
+
Check if a single node is a valid tree node structure (lightweight, doesn't recursively check child nodes). Only checks the structure of the node itself, doesn't check child nodes.
|
|
850
|
+
|
|
851
|
+
```javascript
|
|
852
|
+
// Valid tree node (has children array)
|
|
853
|
+
const node1 = {
|
|
854
|
+
id: 1,
|
|
855
|
+
name: 'node1',
|
|
856
|
+
children: [{ id: 2 }],
|
|
857
|
+
};
|
|
858
|
+
console.log(t.isValidTreeNode(node1)) // true
|
|
859
|
+
|
|
860
|
+
// Valid tree node (no children field)
|
|
861
|
+
const node2 = { id: 1, name: 'node1' };
|
|
862
|
+
console.log(t.isValidTreeNode(node2)) // true
|
|
863
|
+
|
|
864
|
+
// Invalid tree node (children is not an array)
|
|
865
|
+
const invalidNode = {
|
|
866
|
+
id: 1,
|
|
867
|
+
children: 'not an array',
|
|
868
|
+
};
|
|
869
|
+
console.log(t.isValidTreeNode(invalidNode)) // false
|
|
870
|
+
|
|
871
|
+
// Support custom field names
|
|
872
|
+
const customNode = {
|
|
873
|
+
nodeId: 1,
|
|
874
|
+
subNodes: [{ nodeId: 2 }],
|
|
875
|
+
};
|
|
876
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
877
|
+
console.log(t.isValidTreeNode(customNode, fieldNames)) // true
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
**Difference from isSingleTreeData:**
|
|
881
|
+
- `isValidTreeNode` - Only checks the basic structure of a single node, doesn't recursively check child nodes (lightweight)
|
|
882
|
+
- `isSingleTreeData` - Recursively checks the entire tree structure, ensuring all child nodes are valid tree structures
|
|
883
|
+
|
|
884
|
+
### isTreeNodeWithCircularCheck
|
|
885
|
+
|
|
886
|
+
Check if a node is a valid tree node structure and detect circular references. Uses WeakSet to track visited nodes, returns false if circular references are found.
|
|
887
|
+
|
|
888
|
+
```javascript
|
|
889
|
+
// Valid tree node (no circular references)
|
|
890
|
+
const validNode = {
|
|
891
|
+
id: 1,
|
|
892
|
+
children: [
|
|
893
|
+
{ id: 2, children: [{ id: 3 }] },
|
|
894
|
+
],
|
|
895
|
+
};
|
|
896
|
+
console.log(t.isTreeNodeWithCircularCheck(validNode)) // true
|
|
897
|
+
|
|
898
|
+
// Detect circular references
|
|
899
|
+
const node1 = { id: 1, children: [] };
|
|
900
|
+
const node2 = { id: 2, children: [] };
|
|
901
|
+
node1.children.push(node2);
|
|
902
|
+
node2.children.push(node1); // circular reference
|
|
903
|
+
console.log(t.isTreeNodeWithCircularCheck(node1)) // false
|
|
904
|
+
|
|
905
|
+
// Detect self-reference
|
|
906
|
+
const selfRefNode = { id: 1, children: [] };
|
|
907
|
+
selfRefNode.children.push(selfRefNode); // self-reference
|
|
908
|
+
console.log(t.isTreeNodeWithCircularCheck(selfRefNode)) // false
|
|
909
|
+
|
|
910
|
+
// Support custom field names
|
|
911
|
+
const customNode = {
|
|
912
|
+
nodeId: 1,
|
|
913
|
+
subNodes: [{ nodeId: 2 }],
|
|
914
|
+
};
|
|
915
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
916
|
+
console.log(t.isTreeNodeWithCircularCheck(customNode, fieldNames)) // true
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
**Use Cases:**
|
|
920
|
+
- Check for circular references when receiving user input or external data
|
|
921
|
+
- Data validation to prevent infinite recursion
|
|
922
|
+
- Check if data structure is correct during debugging
|
|
923
|
+
|
|
924
|
+
### isSafeTreeDepth
|
|
925
|
+
|
|
926
|
+
Check if the depth of tree-structured data is safe (prevent recursion stack overflow). Returns false if the tree depth exceeds `maxDepth`.
|
|
927
|
+
|
|
928
|
+
```javascript
|
|
929
|
+
// Safe depth tree
|
|
930
|
+
const safeTree = [
|
|
931
|
+
{
|
|
932
|
+
id: 1,
|
|
933
|
+
children: [
|
|
934
|
+
{ id: 2, children: [{ id: 3 }] },
|
|
935
|
+
],
|
|
936
|
+
},
|
|
937
|
+
];
|
|
938
|
+
console.log(t.isSafeTreeDepth(safeTree, 10)) // true (depth is 3, less than 10)
|
|
939
|
+
|
|
940
|
+
// Depth exceeds maximum depth
|
|
941
|
+
const deepTree = [
|
|
942
|
+
{
|
|
943
|
+
id: 1,
|
|
944
|
+
children: [
|
|
945
|
+
{ id: 2, children: [{ id: 3 }] },
|
|
946
|
+
],
|
|
947
|
+
},
|
|
948
|
+
];
|
|
949
|
+
console.log(t.isSafeTreeDepth(deepTree, 2)) // false (depth is 3, exceeds 2)
|
|
950
|
+
|
|
951
|
+
// Empty tree is always safe
|
|
952
|
+
console.log(t.isSafeTreeDepth([], 10)) // true
|
|
953
|
+
|
|
954
|
+
// Single layer tree
|
|
955
|
+
const singleLayer = [{ id: 1 }, { id: 2 }];
|
|
956
|
+
console.log(t.isSafeTreeDepth(singleLayer, 1)) // true
|
|
957
|
+
|
|
958
|
+
// Support custom field names
|
|
959
|
+
const customTree = [
|
|
960
|
+
{
|
|
961
|
+
nodeId: 1,
|
|
962
|
+
subNodes: [
|
|
963
|
+
{ nodeId: 2, subNodes: [{ nodeId: 3 }] },
|
|
964
|
+
],
|
|
965
|
+
},
|
|
966
|
+
];
|
|
967
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
968
|
+
console.log(t.isSafeTreeDepth(customTree, 3, fieldNames)) // true
|
|
969
|
+
console.log(t.isSafeTreeDepth(customTree, 2, fieldNames)) // false
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
**Use Cases:**
|
|
973
|
+
- Check if depth is safe before processing large trees
|
|
974
|
+
- Prevent recursion call stack overflow
|
|
975
|
+
- Performance optimization, avoid processing trees that are too deep
|
|
976
|
+
|
|
977
|
+
## Custom Field Names
|
|
978
|
+
|
|
979
|
+
All methods support custom property names for children and id through the last parameter, passing in a configuration object:
|
|
980
|
+
|
|
981
|
+
```javascript
|
|
982
|
+
// Use default field names
|
|
983
|
+
const foundNode1 = t.findTree(treeData, (node) => node.id === 2)
|
|
984
|
+
|
|
985
|
+
// Use custom field names
|
|
986
|
+
const fieldNames = { children: 'subNodes', id: 'nodeId' };
|
|
987
|
+
const foundNode2 = t.findTree(customTreeData, (node) => node.nodeId === 2, fieldNames);
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
**Note:** All 30 functions support the `fieldNames` parameter to maintain API consistency. Even if the parameter has no effect in certain functions (such as `isEmptyTreeData`), it can still be passed to maintain consistent code style.
|
|
991
|
+
|
|
992
|
+
## Testing
|
|
993
|
+
|
|
994
|
+
### Run Tests
|
|
995
|
+
|
|
996
|
+
```bash
|
|
997
|
+
# Run all tests
|
|
998
|
+
npm test
|
|
999
|
+
|
|
1000
|
+
# Run tests and generate coverage report
|
|
1001
|
+
npm run test:coverage
|
|
1002
|
+
|
|
1003
|
+
# Run tests (once, don't watch for file changes)
|
|
1004
|
+
npm test -- --run
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
## Development
|
|
1008
|
+
|
|
1009
|
+
```bash
|
|
1010
|
+
# Install dependencies
|
|
1011
|
+
npm install
|
|
1012
|
+
|
|
1013
|
+
# Run tests
|
|
1014
|
+
npm test
|
|
1015
|
+
|
|
1016
|
+
# Build project
|
|
1017
|
+
npm run build
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
<div align="center">
|
|
1021
|
+
|
|
1022
|
+
If this project helps you, please give it a ⭐️
|
|
1023
|
+
|
|
1024
|
+
Made with by [knott11]
|
|
1025
|
+
|
|
1026
|
+
</div>
|